-
Notifications
You must be signed in to change notification settings - Fork 2
Add release validator and regression tracking #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8f58256
7d752cb
8ac4dab
d8164f7
609adbc
6455530
e77e4a3
60df837
1ee7b01
aa79701
ecca00d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import { Db } from 'mongodb'; | ||
| import type { ReleaseDBScheme } from '@hawk.so/types'; | ||
|
|
||
| type ReleaseRecordPart = Pick<ReleaseDBScheme, '_id' | 'projectId' | 'release'>; | ||
|
|
||
| /** | ||
| * Mark an event as regressed if it reoccurs in the resolved or a newer release. | ||
| * | ||
| * The update is atomic: only the first repetition after resolution sets the | ||
| * regression release, and later repetitions do not overwrite it. | ||
| * | ||
| * @param db - events database connection | ||
| * @param projectId - project identifier | ||
| * @param groupHash - original event group hash | ||
| * @param release - release in which the event occurred again | ||
| * @param resolvedInRelease - release in which the event was resolved | ||
| * @param regressionInRelease - regression from a previous resolution cycle | ||
| */ | ||
| export async function checkAndMarkRegression( | ||
| db: Db, | ||
| projectId: string, | ||
| groupHash: string, | ||
| release: string, | ||
| resolvedInRelease: string, | ||
| regressionInRelease?: string | ||
| ): Promise<void> { | ||
| const releases = await db.collection<ReleaseRecordPart>('releases').find({ | ||
| projectId, | ||
| release: { | ||
| $in: [resolvedInRelease, release, regressionInRelease].filter(Boolean), | ||
| }, | ||
| }) | ||
| .toArray(); | ||
| const resolvedRelease = releases.find(item => item.release === resolvedInRelease); | ||
| const repetitionRelease = releases.find(item => item.release === release); | ||
| const previousRegressionRelease = regressionInRelease | ||
| ? releases.find(item => item.release === regressionInRelease) | ||
| : undefined; | ||
|
|
||
| if (!resolvedRelease || !repetitionRelease) { | ||
| return; | ||
| } | ||
|
|
||
| const resolvedReleaseId = resolvedRelease._id.toHexString(); | ||
| const isResolvedOrNewerRelease = repetitionRelease._id.toHexString() >= resolvedReleaseId; | ||
| /** | ||
| * A regression in or after the resolved release belongs to the current | ||
| * resolution cycle and must not be overwritten by later repetitions. | ||
| */ | ||
| const hasRegressionForCurrentCycle = previousRegressionRelease && | ||
| previousRegressionRelease._id.toHexString() >= resolvedReleaseId; | ||
|
|
||
| if (!isResolvedOrNewerRelease || hasRegressionForCurrentCycle) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. documentation needed |
||
| return; | ||
| } | ||
|
|
||
| await db.collection(`events:${projectId}`).updateOne({ | ||
| groupHash, | ||
| resolvedInRelease, | ||
| ...(regressionInRelease | ||
| ? { regressionInRelease } | ||
| : { regressionInRelease: { $exists: false } }), | ||
| }, { | ||
| $set: { | ||
| regressionInRelease: release, | ||
| }, | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ import GrouperMetrics from './metrics/grouperMetrics'; | |
| import GrouperMemoryMonitor from './metrics/memoryMonitor'; | ||
| import SlowHandleDiagnostics, { SlowHandleSession } from './metrics/slowHandleDiagnostics'; | ||
| import { grouperDiagnosticsConfig, grouperMemoryConfig } from './metrics/config'; | ||
| import { checkAndMarkRegression } from './check-and-mark-regression'; | ||
|
|
||
| /** | ||
| * eslint does not count decorators as a variable usage | ||
|
|
@@ -343,10 +344,32 @@ export default class GrouperWorker extends Worker { | |
| timestamp: task.timestamp, | ||
| } as RepetitionDBScheme; | ||
|
|
||
| if (task.payload.release) { | ||
| newRepetition.release = task.payload.release; | ||
| } | ||
|
|
||
| repetitionId = await session.measureStep('saveRepetition', () => { | ||
| return this.saveRepetition(task.projectId, newRepetition); | ||
| }); | ||
|
|
||
| if (task.payload.release && existedEvent.resolvedInRelease) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need to check if task.payload.release is newer than existedEvent.resolvedInRelease
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we do this later in the markRegression function.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then we need to rename markRegression to |
||
| try { | ||
| await checkAndMarkRegression( | ||
| this.eventsDb.getConnection(), | ||
| task.projectId, | ||
| uniqueEventHash, | ||
| task.payload.release, | ||
| existedEvent.resolvedInRelease, | ||
| existedEvent.regressionInRelease | ||
| ); | ||
| } catch (error) { | ||
| this.logger.error( | ||
| `[checkAndMarkRegression] project=${task.projectId} groupHash=${uniqueEventHash} release=${task.payload.release}`, | ||
| error | ||
| ); | ||
|
alisawavezen12 marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Clear the large event payload references to allow garbage collection | ||
| * This prevents memory leaks from retaining full event objects after delta is computed | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # Release Validator Worker | ||
|
|
||
| Checks releases after a 24-hour observation period and marks original events that no longer occur as resolved. | ||
|
|
||
| The worker processes releases from oldest to newest, stores the first release without an event in `resolvedInRelease`, and marks successfully processed releases with `fixChecked: true`. | ||
|
|
||
| The worker only uses the fields required for matching releases and event groups. Records without these fields are ignored. Candidate releases must be between 24 hours and 30 days old, while all project releases are still used to compare event history. | ||
|
|
||
| Queue: `cron-tasks/release-validator` | ||
|
|
||
| Run locally: | ||
|
|
||
| ```sh | ||
| yarn run-release-validator | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "hawk-worker-release-validator", | ||
| "version": "0.0.1", | ||
| "description": "Detects events fixed by a release", | ||
| "main": "src/index.ts", | ||
| "author": "CodeX", | ||
| "license": "UNLICENSED", | ||
| "private": true, | ||
| "workerType": "cron-tasks/release-validator" | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.