Skip to content

Commit

Permalink
apdex feature (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeknovy authored Jan 13, 2023
1 parent 8012d46 commit 1ec658b
Show file tree
Hide file tree
Showing 13 changed files with 509 additions and 412 deletions.
9 changes: 9 additions & 0 deletions migrations/1673372364043_apdex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
exports.up = (pgm) => {
pgm.addColumn({ schema: "jtl", name: "scenario" }, {
apdex_settings: {
type: "jsonb",
"default": JSON.stringify({ enabled: false, toleratingThreshold: 400, satisfyingThreshold: 100 }),
notNull: false,
},
})
}
9 changes: 9 additions & 0 deletions migrations/1673597363690_apdex-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
exports.up = (pgm) => {
pgm.addColumn({ schema: "jtl", name: "items" }, {
apdex_settings: {
type: "jsonb",
"default": null,
notNull: false,
},
})
}
4 changes: 2 additions & 2 deletions src/server/controllers/item/get-item-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const getItemController = async (req: IGetUserAuthInfoRequest, res: Respo
// eslint-disable-next-line @typescript-eslint/naming-convention
base_id,
status, hostname, reportStatus, thresholds,
analysisEnabled, zeroErrorToleranceEnabled, topMetricsSettings, name,
analysisEnabled, zeroErrorToleranceEnabled, topMetricsSettings, name, apdexSettings,
} = await db.one(findItem(itemId, projectName, scenarioName))
const { stats: statistics, overview, sutOverview } = await db.one(findItemStats(itemId))

Expand All @@ -37,7 +37,7 @@ export const getItemController = async (req: IGetUserAuthInfoRequest, res: Respo
const maxCpu = findMinMax(monitoring.map(_ => _.avgCpu)).max

res.status(StatusCode.Ok).json({
overview, sutOverview, statistics, status,
overview, sutOverview, statistics, status, apdexSettings,
plot, extraPlotData, note, environment, hostname, reportStatus, thresholds, analysisEnabled,
baseId: base_id, isBase: base_id === itemId, zeroErrorToleranceEnabled, topMetricsSettings,
name,
Expand Down
188 changes: 101 additions & 87 deletions src/server/controllers/item/shared/item-data-processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,116 @@
import { db } from "../../../../db/db"
import { logger } from "../../../../logger"
import {
prepareDataForSavingToDb,
prepareChartDataForSaving,
prepareDataForSavingToDb,
prepareChartDataForSaving,
} from "../../../data-stats/prepare-data"
import { chartQueryOptionInterval } from "../../../data-stats/helper/duration"
import {
saveThresholdsResult, saveItemStats, savePlotData, updateItem,
aggOverviewQuery, aggLabelQuery, chartOverviewQuery,
charLabelQuery, sutOverviewQuery, distributedThreadsQuery, responseCodeDistribution, responseMessageFailures,
deleteSamples,
saveThresholdsResult, saveItemStats, savePlotData, updateItem,
aggOverviewQuery, aggLabelQuery, chartOverviewQuery,
charLabelQuery, sutOverviewQuery, distributedThreadsQuery, responseCodeDistribution, responseMessageFailures,
deleteSamples, calculateApdexValues, updateItemApdexSettings,
} from "../../../queries/items"
import { ReportStatus } from "../../../queries/items.model"
import { getScenarioSettings, currentScenarioMetrics } from "../../../queries/scenario"
import { sendNotifications } from "../../../utils/notifications/send-notification"
import { scenarioThresholdsCalc } from "../utils/scenario-thresholds-calc"

export const itemDataProcessing = async ({ projectName, scenarioName, itemId }) => {
const MAX_LABEL_CHART_LENGTH = 100000
let distributedThreads = null
let sutMetrics = []

try {
const aggOverview = await db.one(aggOverviewQuery(itemId))
const aggLabel = await db.many(aggLabelQuery(itemId))
const statusCodeDistribution = await db.manyOrNone(responseCodeDistribution(itemId))
const responseFailures = await db.manyOrNone(responseMessageFailures(itemId))
const scenarioSettings = await db.one(getScenarioSettings(projectName, scenarioName))


if (aggOverview.number_of_sut_hostnames > 1) {
sutMetrics = await db.many(sutOverviewQuery(itemId))
}


const { overview,
overview: { duration },
labelStats, sutOverview } = prepareDataForSavingToDb(aggOverview, aggLabel, sutMetrics,
statusCodeDistribution, responseFailures)
const defaultInterval = chartQueryOptionInterval(duration)
let chartData
const extraChartData = []

const intervals = [`${defaultInterval} milliseconds`, "5 seconds", "10 seconds", "30 seconds",
"1 minute", "5 minute", "10 minutes", "30 minutes", "1 hour"]
for (const [index, interval] of Object.entries(intervals)) {

// distributed mode
if (aggOverview?.number_of_hostnames > 1) {
distributedThreads = await db.manyOrNone(distributedThreadsQuery(interval, itemId))
}


const labelChart = await db.many(charLabelQuery(interval, itemId))
const overviewChart = await db.many(chartOverviewQuery(interval, itemId))
if (parseInt(index, 10) === 0) { // default interval
chartData = prepareChartDataForSaving(
overviewChart, labelChart, defaultInterval, distributedThreads)
} else if (overviewChart.length > 1 && labelChart.length < MAX_LABEL_CHART_LENGTH) {
const extraChart = prepareChartDataForSaving(
overviewChart, labelChart, defaultInterval, distributedThreads)
extraChartData.push({ interval, data: extraChart })
}

if (!scenarioSettings.extraAggregations) {
break
}
const MAX_LABEL_CHART_LENGTH = 100000
let distributedThreads = null
let sutMetrics = []
let apdex = []

try {
const aggOverview = await db.one(aggOverviewQuery(itemId))
const aggLabel = await db.many(aggLabelQuery(itemId))
const statusCodeDistribution = await db.manyOrNone(responseCodeDistribution(itemId))
const responseFailures = await db.manyOrNone(responseMessageFailures(itemId))
const scenarioSettings = await db.one(getScenarioSettings(projectName, scenarioName))


if (aggOverview.number_of_sut_hostnames > 1) {
sutMetrics = await db.many(sutOverviewQuery(itemId))
}

if (scenarioSettings.apdexSettings.enabled) {
const { satisfyingThreshold, toleratingThreshold } = scenarioSettings.apdexSettings
apdex = await db.many(calculateApdexValues(itemId,
satisfyingThreshold,
toleratingThreshold))
await db.none(updateItemApdexSettings(itemId, {
satisfyingThreshold,
toleratingThreshold,
}))
}


const {
overview,
overview: { duration },
labelStats, sutOverview,
} = prepareDataForSavingToDb(aggOverview, aggLabel, sutMetrics,
statusCodeDistribution, responseFailures, apdex)
const defaultInterval = chartQueryOptionInterval(duration)
let chartData
const extraChartData = []

const intervals = [`${defaultInterval} milliseconds`, "5 seconds", "10 seconds", "30 seconds",
"1 minute", "5 minute", "10 minutes", "30 minutes", "1 hour"]
for (const [index, interval] of Object.entries(intervals)) {

// distributed mode
if (aggOverview?.number_of_hostnames > 1) {
distributedThreads = await db.manyOrNone(distributedThreadsQuery(interval, itemId))
}


const labelChart = await db.many(charLabelQuery(interval, itemId))
const overviewChart = await db.many(chartOverviewQuery(interval, itemId))
if (parseInt(index, 10) === 0) { // default interval
chartData = prepareChartDataForSaving(
overviewChart, labelChart, defaultInterval, distributedThreads)
} else if (overviewChart.length > 1 && labelChart.length < MAX_LABEL_CHART_LENGTH) {
const extraChart = prepareChartDataForSaving(
overviewChart, labelChart, defaultInterval, distributedThreads)
extraChartData.push({ interval, data: extraChart })
}

if (!scenarioSettings.extraAggregations) {
break
}
}

overview.maxVu = Math.max(...chartData.threads.map(([, vu]) => vu))

if (scenarioSettings.thresholdEnabled) {
const scenarioMetrics = await db.one(currentScenarioMetrics(projectName, scenarioName, overview.maxVu))
const thresholdResult = scenarioThresholdsCalc(overview, scenarioMetrics, scenarioSettings)
if (thresholdResult) {
await db.none(saveThresholdsResult(projectName, scenarioName, itemId, thresholdResult))
}
}

await sendNotifications(projectName, scenarioName, itemId, overview)

await db.tx(async t => {
await t.none(saveItemStats(itemId, JSON.stringify(labelStats), overview, JSON.stringify(sutOverview)))
await t.none(savePlotData(itemId, JSON.stringify(chartData), JSON.stringify(extraChartData)))
await t.none(updateItem(itemId, ReportStatus.Ready, overview.startDate))
})

logger.info(`Item: ${itemId} processing finished`)

if (scenarioSettings.deleteSamples) {
logger.info(`Item: ${itemId} deleting samples data`)
await db.none(deleteSamples(itemId))
await db.none("VACUUM FULL jtl.samples")
logger.info(`Item: ${itemId} samples data deletion done`)
}

} catch(error) {
console.log(error)
throw new Error(`Error while processing dataId: ${itemId} for item: ${itemId}, error: ${error}`)
}

overview.maxVu = Math.max(...chartData.threads.map(([, vu]) => vu))

if (scenarioSettings.thresholdEnabled) {
const scenarioMetrics = await db.one(currentScenarioMetrics(projectName, scenarioName, overview.maxVu))
const thresholdResult = scenarioThresholdsCalc(overview, scenarioMetrics, scenarioSettings)
if (thresholdResult) {
await db.none(saveThresholdsResult(projectName, scenarioName, itemId, thresholdResult))
}
}

await sendNotifications(projectName, scenarioName, itemId, overview)

await db.tx(async t => {
await t.none(saveItemStats(itemId, JSON.stringify(labelStats), overview, JSON.stringify(sutOverview)))
await t.none(savePlotData(itemId, JSON.stringify(chartData), JSON.stringify(extraChartData)))
await t.none(updateItem(itemId, ReportStatus.Ready, overview.startDate))
})

logger.info(`Item: ${itemId} processing finished`)

if (scenarioSettings.deleteSamples) {
logger.info(`Item: ${itemId} deleting samples data`)
await db.none(deleteSamples(itemId))
await db.none("VACUUM FULL jtl.samples")
logger.info(`Item: ${itemId} samples data deletion done`)
}

} catch(error) {
console.log(error)
throw new Error(`Error while processing dataId: ${itemId} for item: ${itemId}, error: ${error}`)
}
}
1 change: 1 addition & 0 deletions src/server/controllers/scenario/get-scenario-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ export const getScenarioController = async (req: IGetUserAuthInfoRequest, res: R
userSettings: {
requestStats: userScenarioSettings?.request_stats_settings || defaultRequestStatsSettings,
},
apdexSettings: scenario.apdex_settings,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ describe("updateScenariosController", () => {

},
},
apdexSettings: {
toleratingThreshold: 40,
satisfyingThreshold: 10,
enabled: true,
},
}
const request = {
params: { projectName: "project", scenarioName: "test-scenario" },
Expand All @@ -48,7 +53,7 @@ describe("updateScenariosController", () => {
expect(querySpy).toBeCalledWith("project", "test-scenario", "test-scenario",
body.analysisEnabled, body.thresholds, body.deleteSamples, body.zeroErrorToleranceEnabled,
body.keepTestRunsPeriod, body.generateShareToken, JSON.stringify(body.labelFilterSettings),
JSON.stringify(body.labelTrendChartSettings), body.extraAggregations)
JSON.stringify(body.labelTrendChartSettings), body.extraAggregations, body.apdexSettings)
expect(response.send).toHaveBeenCalledTimes(1)
})
})
46 changes: 23 additions & 23 deletions src/server/controllers/scenario/update-scenario-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@ import { IGetUserAuthInfoRequest } from "../../middleware/request.model"


export const updateScenarioController = async (req: IGetUserAuthInfoRequest, res: Response) => {
const { projectName, scenarioName } = req.params
const { userId } = req.user
const {
thresholds,
analysisEnabled,
scenarioName: name,
deleteSamples,
generateShareToken,
zeroErrorToleranceEnabled,
keepTestRunsPeriod,
labelFilterSettings,
labelTrendChartSettings,
extraAggregations,
userSettings,
} = req.body
const { projectName, scenarioName } = req.params
const { userId } = req.user
const {
thresholds,
analysisEnabled,
scenarioName: name,
deleteSamples,
generateShareToken,
zeroErrorToleranceEnabled,
keepTestRunsPeriod,
labelFilterSettings,
labelTrendChartSettings,
extraAggregations,
userSettings,
apdexSettings,
} = req.body

await db.none(updateScenario(projectName, scenarioName, name, analysisEnabled,
thresholds, deleteSamples, zeroErrorToleranceEnabled, keepTestRunsPeriod,
generateShareToken, JSON.stringify(labelFilterSettings), JSON.stringify(labelTrendChartSettings),
extraAggregations, apdexSettings))

await db.none(updateScenario(projectName, scenarioName, name, analysisEnabled,
thresholds, deleteSamples, zeroErrorToleranceEnabled, keepTestRunsPeriod,
generateShareToken, JSON.stringify(labelFilterSettings), JSON.stringify(labelTrendChartSettings),
extraAggregations))
await db.none(updateUserScenarioSettings(projectName, scenarioName, userId,
JSON.stringify(userSettings.requestStats)))

await db.none(updateUserScenarioSettings(projectName, scenarioName, userId,
JSON.stringify(userSettings.requestStats)))

res.status(StatusCode.NoContent).send()
res.status(StatusCode.NoContent).send()
}
13 changes: 12 additions & 1 deletion src/server/data-stats/prepare-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,11 @@ describe("prepare data", () => {
{ label: "label1", response_message: "failure", count: 31, status_code: "100", failure_message: "failure" },
{ label: "label2", response_message: "failure2", count: 1, status_code: "101", failure_message: "failure1" },
]
const apdex = [
{ label: "label1", toleration: "40", satisfaction: "200" },
{ label: "label2", toleration: "30", satisfaction: "100" }]
const { overview, labelStats } = prepareDataForSavingToDb(overviewData, labelsData, [],
statusCodes, responseFailures)
statusCodes, responseFailures, apdex)
expect(overview).toEqual({
percentil: 271,
maxVu: undefined,
Expand Down Expand Up @@ -306,6 +309,10 @@ describe("prepare data", () => {
responseMessageFailures: [{
count: 31, responseMessage: "failure", statusCode: "100", failureMessage: "failure",
}],
apdex: {
satisfaction: 200,
toleration: 40,
},
},
{
label: "label2",
Expand All @@ -326,6 +333,10 @@ describe("prepare data", () => {
responseMessageFailures: [{
count: 1, responseMessage: "failure2", statusCode: "101", failureMessage: "failure1",
}],
apdex: {
toleration: 30,
satisfaction: 100,
},
}])
})
})
Expand Down
Loading

0 comments on commit 1ec658b

Please sign in to comment.