This is an automated email from the ASF dual-hosted git repository. michaelsmith pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/impala.git
commit e9acc12a86c57e4456e35efe84fe4ecaeaa6f52a Author: Surya Hebbar <[email protected]> AuthorDate: Thu Oct 5 22:11:59 2023 +0530 IMPALA-12415: Implement tests for graphical query timeline in webUI To help with testing the query timeline web page, the tests have been integrated using JEST testing framework based on nodejs. 'run_js_tests.sh' runs JEST test suits, after it fetches nodejs binaries and related nodejs packages, if they are not present locally. NodeJS binaries are stored within the ${IMPALA_TOOLCHAIN} directory. 'jest-environment-jsdom' supports rendering DOM elements and testing them without the requirement of a browser. For implementing unit and integration tests, the script has been divided into multiple properly functioning modules, that are imported as ES6 modules. Unit tests have been written for the following query profile parsing functions. - mapTimeseriesCounters() - Test whether the method correctly searches and maps the profile's counter's indexes based on counter_name even in reverse order - accumulateTimeseriesValues() - Test whether the method correctly accumlates values after parsing values from 'data' in 'time_series_counters' while updating 'max_samples' - generateTimesamples() - Test whether time sample values generated based on 'max_samples' are correct, with different 'max_samples' - clearTimeseriesValues() - Test whether Timeseries arrays are being properly truncated in the correct range - initializeUtilizationMetrics() - Test whether aggregate arrays and time sample arrays are correctly allocated based on counters and 'max_samples' - getSvg*() - Test whether all getSvg methods are correctly setting attributes and returning expected attributes in elements Unit tests produce JUnitXML for integration with jenkins jobs, these are stored in ${IMPALA_JS_TEST_LOGS_DIR}. Change-Id: I0caf0a0beee23821f78c0b3fe1aeb7dbf92d6a3e Reviewed-on: http://gerrit.cloudera.org:8080/20538 Reviewed-by: Wenzhe Zhou <[email protected]> Tested-by: Impala Public Jenkins <[email protected]> --- bin/impala-config.sh | 1 + bin/run-all-tests.sh | 8 + tests/run-js-tests.sh | 58 +++ www/scripts/query_timeline/chart_commons.js | 11 + www/scripts/query_timeline/fragment_diagram.js | 7 + www/scripts/query_timeline/global_dom.js | 25 ++ .../query_timeline/host_utilization_diagram.js | 7 + www/scripts/query_timeline/package.json | 5 + www/scripts/tests/.gitignore | 2 + www/scripts/tests/package.json | 20 + .../tests/query_timeline/chart_commons.test.js | 464 +++++++++++++++++++++ .../tests/query_timeline/fragment_diagram.test.js | 66 +++ .../host_utilization_diagram.test.js | 73 ++++ 13 files changed, 747 insertions(+) diff --git a/bin/impala-config.sh b/bin/impala-config.sh index 88ac95d8d..74c24c558 100755 --- a/bin/impala-config.sh +++ b/bin/impala-config.sh @@ -870,6 +870,7 @@ export IMPALA_FE_TEST_LOGS_DIR="${IMPALA_LOGS_DIR}/fe_tests" export IMPALA_FE_TEST_COVERAGE_DIR="${IMPALA_FE_TEST_LOGS_DIR}/coverage" export IMPALA_BE_TEST_LOGS_DIR="${IMPALA_LOGS_DIR}/be_tests" export IMPALA_EE_TEST_LOGS_DIR="${IMPALA_LOGS_DIR}/ee_tests" +export IMPALA_JS_TEST_LOGS_DIR="${IMPALA_LOGS_DIR}/js_tests" export IMPALA_CUSTOM_CLUSTER_TEST_LOGS_DIR="${IMPALA_LOGS_DIR}/custom_cluster_tests" export IMPALA_MVN_LOGS_DIR="${IMPALA_LOGS_DIR}/mvn" export IMPALA_TIMEOUT_LOGS_DIR="${IMPALA_LOGS_DIR}/timeout_stacktrace" diff --git a/bin/run-all-tests.sh b/bin/run-all-tests.sh index a114aacaa..b6472e5e6 100755 --- a/bin/run-all-tests.sh +++ b/bin/run-all-tests.sh @@ -61,6 +61,8 @@ fi # Run Cluster Tests : ${CLUSTER_TEST:=true} : ${CLUSTER_TEST_FILES:=} +# Run JS tests +: ${JS_TEST:=false} # Verifiers to run after all tests. Skipped if empty. : ${TEST_SUITE_VERIFIERS:=verifiers/test_banned_log_messages.py} : ${TEST_SUITE_VERIFIERS_LOG_DIR:=${IMPALA_LOGS_DIR}/verifiers} @@ -349,6 +351,12 @@ do fi fi + if [[ "$JS_TEST" == true ]]; then + if ! "${IMPALA_HOME}/tests/run-js-tests.sh"; then + TEST_RET_CODE=1 + fi + fi + if [[ "$JDBC_TEST" == true ]]; then # Run the JDBC tests with background loading disabled. This is interesting because # it requires loading missing table metadata. diff --git a/tests/run-js-tests.sh b/tests/run-js-tests.sh new file mode 100755 index 000000000..3a9097c62 --- /dev/null +++ b/tests/run-js-tests.sh @@ -0,0 +1,58 @@ +#!/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. + +set -euo pipefail +. "$IMPALA_HOME/bin/report_build_error.sh" +setup_report_build_error + +: ${IMPALA_JS_TEST_LOGS_DIR:="${IMPALA_LOGS_DIR}/js_tests"} + +NODEJS_VERSION=v16.20.2 +NODEJS_DISTRO=linux-x64 +NODEJS_LIB_PATH="${IMPALA_TOOLCHAIN}/node-${NODEJS_VERSION}" +export IMPALA_NODEJS="${NODEJS_LIB_PATH}/bin/node" +NPM="${NODEJS_LIB_PATH}/bin/npm" +JS_TESTS_DIR="${IMPALA_HOME}/www/scripts/tests" + +export IMPALA_JS_TEST_LOGS_DIR; + +# Install nodejs locally, if not installed +if [ -r "$IMPALA_NODEJS" ]; then + echo "NodeJS ${NODEJS_VERSION} installation found"; +else + echo "Fetching NodeJS ${NODEJS_VERSION}-${NODEJS_DISTRO} binaries ..."; + NODE_URL_PREFIX="https://nodejs.org/dist" + NODE_URL_SUFFIX="${NODEJS_VERSION}/node-${NODEJS_VERSION}-${NODEJS_DISTRO}.tar.xz" + curl "${NODE_URL_PREFIX}/${NODE_URL_SUFFIX}" -O + + tar -xJf node-${NODEJS_VERSION}-${NODEJS_DISTRO}.tar.xz + + mkdir -p "${NODEJS_LIB_PATH}" + + mv node-${NODEJS_VERSION}-${NODEJS_DISTRO}/* -t "${NODEJS_LIB_PATH}"; + + rm -rf node-${NODEJS_VERSION}-${NODEJS_DISTRO}.tar.xz \ + node-${NODEJS_VERSION}-${NODEJS_DISTRO}/ +fi; + +# Install packages in package.json +"$IMPALA_NODEJS" "$NPM" --prefix "${JS_TESTS_DIR}" install + +# Run all JEST testing suites (by default *.test.js) +"$IMPALA_NODEJS" "$NPM" --prefix "${JS_TESTS_DIR}" test diff --git a/www/scripts/query_timeline/chart_commons.js b/www/scripts/query_timeline/chart_commons.js index 11a087596..85f9e4ec0 100644 --- a/www/scripts/query_timeline/chart_commons.js +++ b/www/scripts/query_timeline/chart_commons.js @@ -18,6 +18,8 @@ import {maxts, set_maxts, clearDOMChildren} from "./global_members.js"; import {name_width} from "./fragment_diagram.js"; +export var exportedForTest; + function accumulateTimeseriesValues(values_array, time_series_counter, max_samples) { var samples = time_series_counter.data.split(",").map(el => parseInt(el)); var max_traverse_len = Math.min(samples.length, values_array.length - 2); @@ -60,11 +62,16 @@ export function generateTimesamples(timesamples_array, max_samples, extend) { export function mapTimeseriesCounters(time_series_counters, counters) { for (var i = 0; i < counters.length; i++) { + var no_change = true; for (var j = 0; j < time_series_counters.length; j++) { if (time_series_counters[j].counter_name == counters[i][0]) { counters[i][2] = j; + no_change = false; } } + if (no_change) { + throw new Error(`"${counters[i][0]}" not found within profile`); + } } } @@ -108,3 +115,7 @@ export function destroyChart(chart, chart_dom_obj) { clearDOMChildren(chart_dom_obj); return null; } + +if (typeof process != "undefined" && process.env.NODE_ENV === 'test') { + exportedForTest = {accumulateTimeseriesValues}; +} diff --git a/www/scripts/query_timeline/fragment_diagram.js b/www/scripts/query_timeline/fragment_diagram.js index 74f8b899b..b3e22f354 100644 --- a/www/scripts/query_timeline/fragment_diagram.js +++ b/www/scripts/query_timeline/fragment_diagram.js @@ -21,6 +21,9 @@ import {profile, set_maxts, maxts, decimals, set_decimals, diagram_width, resizeHorizontalAll} from "./global_members.js"; import {host_utilization_chart, getUtilizationHeight} from "./host_utilization_diagram.js"; +import "./global_dom.js"; + +export var exportedForTest; export var name_width; export var page_additional_height; @@ -600,3 +603,7 @@ timeticks_footer.addEventListener('wheel', function(e) { }); plan_order.addEventListener('click', renderFragmentDiagram); + +if (typeof process != "undefined" && process.env.NODE_ENV === 'test') { + exportedForTest = {getSvgRect, getSvgLine, getSvgText, getSvgTitle, getSvgGroup}; +} diff --git a/www/scripts/query_timeline/global_dom.js b/www/scripts/query_timeline/global_dom.js new file mode 100644 index 000000000..10e57f734 --- /dev/null +++ b/www/scripts/query_timeline/global_dom.js @@ -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. + +if (typeof process != "undefined" && process.env.NODE_ENV === 'test' && + document.body.innerHTML === "") { + const fs = await import('fs'); + document.body.innerHTML = fs.readFileSync(`${process.env.IMPALA_HOME}/www/query_timeline.tmpl`, "utf-8") + .replace(/{{(.*?)}}/g, "") + .replace(/<script\b[^>]*>(.*?)<\/script>/gs, "") + .replace(/<link\b[^>]*>/gs, ""); +} diff --git a/www/scripts/query_timeline/host_utilization_diagram.js b/www/scripts/query_timeline/host_utilization_diagram.js index 7d29746e0..b3e820417 100644 --- a/www/scripts/query_timeline/host_utilization_diagram.js +++ b/www/scripts/query_timeline/host_utilization_diagram.js @@ -22,6 +22,9 @@ import {name_width, page_additional_height, setTimingDiagramDimensions} from "./fragment_diagram.js"; import {aggregateProfileTimeseries, generateTimesamples, clearTimeseriesValues, mapTimeseriesCounters, displayWarning, destroyChart} from "./chart_commons.js"; +import "./global_dom.js"; + +export var exportedForTest; // #host_utilization_diagram export var host_utilization_visible = true; @@ -225,3 +228,7 @@ export function collectUtilizationFromProfile() { console.log(e); } } + +if (typeof process != "undefined" && process.env.NODE_ENV === 'test') { + exportedForTest = {initializeUtilizationMetrics}; +} diff --git a/www/scripts/query_timeline/package.json b/www/scripts/query_timeline/package.json new file mode 100644 index 000000000..92b9ca8ae --- /dev/null +++ b/www/scripts/query_timeline/package.json @@ -0,0 +1,5 @@ +{ + "name": "Impala WebUI - Query Timeline", + "version": "1.0.0", + "type": "module" +} diff --git a/www/scripts/tests/.gitignore b/www/scripts/tests/.gitignore new file mode 100644 index 000000000..504afef81 --- /dev/null +++ b/www/scripts/tests/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/www/scripts/tests/package.json b/www/scripts/tests/package.json new file mode 100644 index 000000000..32f981421 --- /dev/null +++ b/www/scripts/tests/package.json @@ -0,0 +1,20 @@ +{ + "name": "Impala WebUI JS Tests", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "JEST_JUNIT_OUTPUT_DIR=\"${IMPALA_JS_TEST_LOGS_DIR}\" \"${IMPALA_NODEJS}\" --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "devDependencies": { + "jest": "^29.6.4", + "jest-environment-jsdom": "^29.6.4", + "jest-junit": "^16.0.0" + }, + "jest": { + "testEnvironment": "jsdom", + "reporters": [ "default", "jest-junit" ] + }, + "jest-junit": { + "outputName": "js-tests.xml" + } +} diff --git a/www/scripts/tests/query_timeline/chart_commons.test.js b/www/scripts/tests/query_timeline/chart_commons.test.js new file mode 100644 index 000000000..62c688a29 --- /dev/null +++ b/www/scripts/tests/query_timeline/chart_commons.test.js @@ -0,0 +1,464 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT 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 {describe, test, expect} from '@jest/globals'; +import {exportedForTest, generateTimesamples, clearTimeseriesValues, + mapTimeseriesCounters, aggregateProfileTimeseries} from + '../../query_timeline/chart_commons.js'; + +describe("Test mapTimeseriesCounters", () => { + // Test whether the method correctly searches and maps indexes of counters based + // on counter_name + test("Basic Test (Serial Order)", () => { + var parent_profile = + { + "profile_name": "Per Node Profiles", + "num_children": 3, + "child_profiles": [ + { + "profile_name": "host-1:27000", + "time_series_counters": [{ + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + } + ] + }; + var counters = [ + ["HostCpuIoWaitPercentage", "avg io wait", 0], + ["HostCpuSysPercentage", "avg sys", 0], + ["HostCpuUserPercentage", "avg user", 0] + ]; + expect(mapTimeseriesCounters(parent_profile.child_profiles[0].time_series_counters, + counters)).toBe(undefined); + for (var i = 0; i < counters.length; i++) { + expect(counters[i][2]).toBe(i); + } + }); + + test("Basic Test (Reverse Order)", () => { + var parent_profile = + { + "profile_name": "Per Node Profiles", + "num_children": 3, + "child_profiles": [ + { + "profile_name": "host-1:27000", + "time_series_counters": [{ + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + } + ] + }; + var counters = [ + ["HostCpuIoWaitPercentage", "avg io wait", 0], + ["HostCpuSysPercentage", "avg sys", 0], + ["HostCpuUserPercentage", "avg user", 0] + ]; + expect(mapTimeseriesCounters(parent_profile.child_profiles[0].time_series_counters, + counters)).toBe(undefined); + for (var i = 0; i < counters.length; i++) { + expect(counters[i][2]).toBe(counters.length - i - 1); + } + }); + + test("Edge Case (No such 'counter_name' within profile)", () => { + var parent_profile = + { + "profile_name": "Per Node Profiles", + "num_children": 3, + "child_profiles": [ + { + "profile_name": "host-1:27000", + "time_series_counters": [{ + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + } + ] + }; + var counters = [ + ["HostPercentage", "avg io wait", 0], + ["HostSysPercenage", "avg sys", 0], + ["HostUserPercntage", "avg user", 0] + ]; + try { + mapTimeseriesCounters(parent_profile.child_profiles[0].time_series_counters, + counters); + } catch(e) { + expect(e.message).toBe(`"${counters[0][0]}" not found within profile`); + } + }); +}); + +describe("Test accumulateTimeseriesValues", () => { + // Test whether the method correctly accumlates values after parsing values from 'data' + // in 'time_series_counters' and correctly updates 'max_samples' even in corner cases + var {accumulateTimeseriesValues} = exportedForTest; + var data_type = "value type"; + test("Basic Case (time_series_counter.num > max_samples.collected)", () => { + var max_samples = { + allocated : 7, + period : 0, + available : 0, + collected : 0 + }; + var values_array = [data_type, 0, 60, 100, 40, 38, 49, 61, 27]; + var time_series_counter = { + period: 100, + num: 2000, + data: "30, 100, 40" + }; + + expect(accumulateTimeseriesValues(values_array, time_series_counter, max_samples)) + .toBe(undefined); + + expect(values_array).toEqual([data_type, 0, 90, 200, 80, 38, 49, 61, 27]); + + expect(max_samples).toEqual({ + allocated : 7, + period : 100, + available : 3, + collected : 2000 + }); + }); + + test("Basic Case (time_series_counter.period > max_samples.period", () => { + var max_samples = { + allocated : 7, + period : 100, + available : 1000, + collected : 1000 + }; + var values_array = [data_type, 0, 60, 100, 40, 38, 49, 61, 27]; + var time_series_counter = { + period: 200, + num: 300, + data: "30, 100, 40" + }; + + expect(accumulateTimeseriesValues(values_array, time_series_counter, max_samples)) + .toBe(undefined); + + expect(values_array).toEqual([data_type, 0, 90, 200, 80, 38, 49, 61, 27]); + + expect(max_samples).toEqual({ + allocated : 7, + period : 200, + available : 3, + collected : 300 + }); + }); + + test(`Basic Case (time_series_counter.period <= max_samples.period + && time_series_counter.num <= max_samples.collected)`, () => { + var max_samples = { + allocated : 7, + period : 100, + available : 1000, + collected : 1000 + }; + var values_array = [data_type, 0, 60, 100, 40, 38, 49, 61, 27]; + var time_series_counter = { + period: 100, + num: 300, + data: "30, 100, 40" + }; + + expect(accumulateTimeseriesValues(values_array, time_series_counter, max_samples)) + .toBe(undefined); + + expect(values_array).toEqual([data_type, 0, 90, 200, 80, 38, 49, 61, 27]); + + expect(max_samples).toEqual({ + allocated : 7, + period : 100, + available : 1000, + collected : 1000 + }); + }); + + test(`Edge Case (values_array length is smaller than collected samples)`, () => { + var max_samples = { + allocated : 2, + period : 100, + available : 2, + collected : 1000 + }; + var values_array = [data_type, 0, 60, 100]; + var time_series_counter = { + period: 100, + num: 300, + data: "30, 100, 40" + }; + + expect(accumulateTimeseriesValues(values_array, time_series_counter, max_samples)) + .toBe(undefined); + + expect(values_array).toEqual([data_type, 0, 90, 200]); + + expect(max_samples).toEqual({ + allocated : 2, + period : 100, + available : 2, + collected : 1000 + }); + }); +}); + +describe("Test generateTimesamples", () => { + // Test whether time sample values generated based on 'max_samples' are correct, + // even in corner cases, with different 'max_samples' scenarios + var data_type = "timesample type"; + test("Basic Case (max_samples.allocated > max_samples.available)", () => { + var max_samples = { + allocated : 10, + period : 1000, + available : 4, + collected : 10 + }; + var timesamples_array = new Array(max_samples.allocated + 2).fill(null); + timesamples_array[0] = data_type; + + expect(generateTimesamples(timesamples_array, max_samples)).toBe(undefined); + + expect(timesamples_array).toEqual([data_type, 0, 2.5, 5, 7.5, 10, null, null, + null, null, null, null]); + }); + + test("Edge Case (max_samples.allocated < max_samples.available)", () => { + var max_samples = { + allocated : 10, + period : 1000, + available : 20, + collected : 10 + }; + var timesamples_array = new Array(max_samples.allocated + 2); + timesamples_array[0] = data_type; + + expect(generateTimesamples(timesamples_array, max_samples)).toBe(undefined); + + expect(timesamples_array).toEqual([data_type, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, + 4.5, 5]); + }); + + test("Edge Case (max_samples.allocated = max_samples.available)", () => { + var max_samples = { + allocated : 10, + period : 1000, + available : 10, + collected : 10 + }; + var timesamples_array = new Array(max_samples.allocated + 2); + timesamples_array[0] = data_type; + + expect(generateTimesamples(timesamples_array, max_samples)).toBe(undefined); + + expect(timesamples_array).toEqual([data_type, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }); +}); + +describe("Test clearTimeseriesValues", () => { + // Test whether Timeseries arrays are being properly truncated in the correct range + var data_type = "value type"; + test("Basic Case (max_samples.available < max_samples.allocated)", () => { + var max_samples = { + allocated : 7, + period : 1000, + available : 3, + collected : 10 + }; + var values_array = [data_type, 0, 4, 3, 20, 10, 100, 10]; + + expect(clearTimeseriesValues(values_array, max_samples)).toBe(undefined); + + expect(values_array).toEqual([data_type, 0, null, null, null, 10, 100, 10]); + }); + + test("Edge Case (max_samples.available >= max_samples.allocated)", () => { + var max_samples = { + allocated : 7, + period : 1000, + available : 30, + collected : 10 + }; + var values_array = [data_type, 0, 3, 4, 3, 20, 10, 100, 10]; + + expect(clearTimeseriesValues(values_array, max_samples)).toBe(undefined); + + expect(values_array).toEqual([data_type, 0, null, null, null, null, null, null, null]); + }); +}); + +describe("Test aggregateProfileTimeseries", () => { + // Test correctness of values being aggregated from parsing the profile + test("Basic Case", () => { + var parent_profile = + { + "profile_name": "Per Node Profiles", + "num_children": 3, + "child_profiles": [ + { + "profile_name": "host-1:27000", + "time_series_counters": [{ + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + }, + { + "profile_name": "host-1:27001", + "time_series_counters": [{ + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + }, + { + "profile_name": "host-1:27001", + "time_series_counters": [{ + "counter_name": "HostCpuIoWaitPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }, { + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + } + ] + }; + var max_samples = { + allocated : 10, + period : 0, + available : 0, + collected : 0 + }; + var counters = [ + ["HostCpuIoWaitPercentage", "avg io wait", 0], + ["HostCpuSysPercentage", "avg sys", 0], + ["HostCpuUserPercentage", "avg user", 0] + ]; + var aggregate_array = new Array(counters.length); + for (var i = 0; i < counters.length; ++i) { + aggregate_array[i] = new Array(max_samples.allocated + 2).fill(0); + aggregate_array[i][0] = counters[i][1]; + } + mapTimeseriesCounters(parent_profile.child_profiles[0].time_series_counters, + counters); + + expect(aggregateProfileTimeseries(parent_profile, aggregate_array, counters, + max_samples)).toBe(undefined); + + expect(aggregate_array).toEqual([ + ['avg io wait', 0, 0, 0, 0, 210, 0, 0, 0, 0, 0, 30], + ['avg sys', 0, 936, 2037, 1335,1320, 903, 903, 936, 375, 375, 1311], + ['avg user', 0, 936, 2037, 1335, 1320, 903, 903, 936, 375, 375, 1311] + ]); + + expect(max_samples).toEqual({ + allocated : 10, + period : 100, + available : 10, + collected : 59 + }); + }); +}); diff --git a/www/scripts/tests/query_timeline/fragment_diagram.test.js b/www/scripts/tests/query_timeline/fragment_diagram.test.js new file mode 100644 index 000000000..708b761a5 --- /dev/null +++ b/www/scripts/tests/query_timeline/fragment_diagram.test.js @@ -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. + +import {describe, test, expect} from '@jest/globals'; +import {exportedForTest} from "../../query_timeline/fragment_diagram.js"; + +describe("Test getSvg*", () => { + // Test whether getSvg* methods correctly set attributes and return expected elements + var {getSvgRect, getSvgLine, getSvgText, getSvgTitle, getSvgGroup} = exportedForTest; + var stroke_fill_colors = { black : "#000000", dark_grey : "#505050", + light_grey : "#F0F0F0", transperent : "rgba(0, 0, 0, 0)" }; + + test("Test getSvgRect", () => { + expect(getSvgRect(stroke_fill_colors.transperent, 0, 0, 100, 100, true, + stroke_fill_colors.black).outerHTML).toBe( + '<rect x="0px" y="0px" width="100px" height="100px"' + + ` fill="${stroke_fill_colors.transperent}"` + + ` stroke="${stroke_fill_colors.black}"` + + ` stroke-dasharray="2 2"></rect>`); + }); + + test("Test getSvgLine", () => { + expect(getSvgLine(stroke_fill_colors.black, 0, 0, 100, 100, true).outerHTML).toBe( + '<line x1="0px" y1="0px" x2="100px" y2="100px"' + + ` stroke="${stroke_fill_colors.black}"` + + ' stroke-dasharray="2 2"></line>'); + }); + + test("Test getSvgText", () => { + expect(getSvgText("Text", stroke_fill_colors.black, 0, 0, 15, true, 300) + .outerHTML).toBe( + '<text x="0px" y="0px" style="font-size: 10px;" dominant-baseline="middle" ' + + `text-anchor="middle" fill="${stroke_fill_colors.black}" textLength="300" ` + + 'lengthAdjust="spacingAndGlyphs">Text</text>'); + }); + + test("Test getSvgText", () => { + expect(getSvgText("Text", stroke_fill_colors.black, 0, 0, 15, true, 300) + .outerHTML).toBe( + '<text x="0px" y="0px" style="font-size: 10px;" dominant-baseline="middle" ' + + `text-anchor="middle" fill="${stroke_fill_colors.black}" textLength="300" ` + + 'lengthAdjust="spacingAndGlyphs">Text</text>'); + }); + + test("Test getSvgTitle", () => { + expect(getSvgTitle("Title").outerHTML).toBe("<title>Title</title>"); + }); + + test("Test getSvgGroup", () => { + expect(getSvgGroup().outerHTML).toBe("<g></g>"); + }); +}); diff --git a/www/scripts/tests/query_timeline/host_utilization_diagram.test.js b/www/scripts/tests/query_timeline/host_utilization_diagram.test.js new file mode 100644 index 000000000..0b8ba565c --- /dev/null +++ b/www/scripts/tests/query_timeline/host_utilization_diagram.test.js @@ -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. + +import {describe, test, expect} from '@jest/globals'; +import {exportedForTest} from "../../query_timeline/host_utilization_diagram.js"; + +describe("Test initializeUtilizationMetrics", () => { + // Test whether aggregate arrays and time sample arrays are correctly allocated + // based on counters and max_samples + var {initializeUtilizationMetrics} = exportedForTest; + test("Basic Test", () => { + var parent_profile = + { + "profile_name": "Per Node Profiles", + "num_children": 3, + "child_profiles": [ + { + "profile_name": "host-1:27000", + "time_series_counters": [{ + "counter_name": "HostCpuUserPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "0,0,0,70,0,0,0,0,0,10" + }, { + "counter_name": "HostCpuSysPercentage", + "unit": "BASIS_POINTS", + "num": 59, + "period": 100, + "data": "312,679,445,440,301,301,312,125,125,437" + }] + } + ] + }; + var max_samples = { + allocated : 3, + period : 0, + available : 0, + collected : 0 + }; + var counters_y1 = [ + ["HostCpuUserPercentage", "avg io wait", 0], + ["HostCpuSysPercentage", "avg sys", 0] + ]; + var timeaxis_name = "utilization timeticks"; + var {cpu_nodes_usage_aggregate, sampled_utilization_timeseries} = + initializeUtilizationMetrics(parent_profile, counters_y1, max_samples, + timeaxis_name); + expect(cpu_nodes_usage_aggregate).toEqual([ + [counters_y1[0][1], 0, null, null, null], + [counters_y1[1][1], 0, null, null, null] + ]); + expect(sampled_utilization_timeseries).toEqual( + [timeaxis_name, null, null, null, null] + ); + expect(counters_y1[0][2]).toBe(0); + expect(counters_y1[1][2]).toBe(1); + }); +});
