Skip to content

Commit 095e0d5

Browse files
committed
refactor(metrics): update DORA metrics calculation logic, replace DoraMetrics with dto.Metrics, and enhance metrics processing functions
1 parent 3aafabf commit 095e0d5

2 files changed

Lines changed: 89 additions & 75 deletions

File tree

lens/api/RestHandler.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,7 @@ func (impl *RestHandlerImpl) GetDeploymentMetrics(w http.ResponseWriter, r *http
134134
metricRequest.To = to
135135
}
136136

137-
//err := decoder.Decode(metricRequest)
138-
//if err != nil {
139-
// impl.logger.Error(err)
140-
// impl.writeJsonResp(w, err, nil, http.StatusBadRequest)
141-
// return
142-
//}
143-
metrics, err := impl.deploymentMetricService.GetDeploymentMetrics(metricRequest)
137+
metrics, err := impl.deploymentMetricService.ProcessSingleDoraMetrics(metricRequest)
144138
impl.logger.Infof("metrics %+v", metrics)
145139
impl.writeJsonResp(w, err, metrics, 200)
146140
}

lens/pkg/DeploymentMetricService.go

Lines changed: 88 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type DeploymentMetricService interface {
3232
GetBulkDeploymentMetrics(request *dto.BulkMetricRequest) (*dto.BulkMetricsResponse, error)
3333

3434
// New DORA metrics functions
35-
ProcessSingleDoraMetrics(request *dto.MetricRequest) (*DoraMetrics, error)
35+
ProcessSingleDoraMetrics(request *dto.MetricRequest) (*dto.Metrics, error)
3636
ProcessBulkDoraMetrics(request *dto.BulkMetricRequest) ([]DoraMetrics, error)
3737
CalculateDoraMetrics(appId, envId int, releases []sql.AppRelease, materials []*sql.PipelineMaterial, leadTimes []sql.LeadTime, fromTime, toTime time.Time) *DoraMetrics
3838
GetDoraMetricsSummary(doraMetrics *DoraMetrics) *DoraMetricsSummary
@@ -805,8 +805,78 @@ func (impl DeploymentMetricServiceImpl) ProcessBulkDoraMetrics(request *dto.Bulk
805805
return impl.CalculateDoraMetricsForBulk(request.AppEnvPairs, releasesByAppEnv, allLeadTimes, *request.From, *request.To), nil
806806
}
807807

808+
// calculateCycleTimeBetweenReleases calculates the time between consecutive releases
809+
func (impl DeploymentMetricServiceImpl) calculateCycleTimeBetweenReleases(releases []*dto.Metric, lastRelease *sql.AppRelease) {
810+
if len(releases) == 0 {
811+
return
812+
}
813+
814+
// Calculate cycle time between consecutive releases
815+
for i := 0; i < len(releases)-1; i++ {
816+
releases[i].CycleTime = releases[i].ReleaseTime.Sub(releases[i+1].ReleaseTime).Minutes()
817+
}
818+
819+
// Handle the last release
820+
if lastRelease != nil {
821+
releases[len(releases)-1].CycleTime = releases[len(releases)-1].ReleaseTime.Sub(lastRelease.TriggerTime).Minutes()
822+
} else if len(releases) > 0 {
823+
releases[len(releases)-1].CycleTime = 0
824+
}
825+
}
826+
827+
// populateMetricsWithImprovedLogic populates dto.Metrics using DORA calculation helper functions
828+
func (impl DeploymentMetricServiceImpl) populateMetricsWithImprovedLogic(appReleases []sql.AppRelease, materials []*sql.PipelineMaterial, leadTimes []sql.LeadTime, lastRelease *sql.AppRelease, fromTime, toTime time.Time) (*dto.Metrics, error) {
829+
releases := impl.transform(appReleases, materials, leadTimes)
830+
831+
impl.calculateCycleTimeBetweenReleases(releases, lastRelease)
832+
833+
deploymentFrequency := impl.calculateDeploymentFrequency(releases, fromTime, toTime)
834+
averageLeadTime := impl.calculateMeanLeadTimeForChanges(releases)
835+
changeFailureRate := impl.calculateChangeFailureRateNew(releases)
836+
averageRecoveryTime := impl.calculateMeanTimeToRecovery(releases)
837+
838+
lastFailedTime := ""
839+
recoveryTimeLastFailed := float64(0)
840+
for i := 0; i < len(releases); i++ {
841+
if releases[i].ReleaseStatus == dto.Failure {
842+
if lastFailedTime == "" {
843+
lastFailedTime = releases[i].ReleaseTime.Format(constants.Layout)
844+
}
845+
if i < len(releases)-1 && releases[i+1].ReleaseStatus == dto.Failure {
846+
continue
847+
}
848+
for j := i - 1; j >= 0; j-- {
849+
if releases[j].ReleaseStatus == dto.Success {
850+
releases[i].RecoveryTime = releases[j].ReleaseTime.Sub(releases[i].ReleaseTime).Minutes()
851+
if recoveryTimeLastFailed == 0 {
852+
recoveryTimeLastFailed = releases[i].RecoveryTime
853+
}
854+
break
855+
}
856+
}
857+
}
858+
}
859+
860+
metrics := &dto.Metrics{
861+
Series: releases,
862+
AverageCycleTime: deploymentFrequency,
863+
AverageLeadTime: averageLeadTime,
864+
ChangeFailureRate: changeFailureRate,
865+
AverageRecoveryTime: averageRecoveryTime,
866+
LastFailedTime: lastFailedTime,
867+
RecoveryTimeLastFailed: recoveryTimeLastFailed,
868+
}
869+
870+
// Calculate change size metrics
871+
if len(metrics.Series) > 0 {
872+
impl.calculateChangeSize(metrics)
873+
}
874+
875+
return metrics, nil
876+
}
877+
808878
// ProcessSingleDoraMetrics processes DORA metrics for a single app-env pair
809-
func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.MetricRequest) (*DoraMetrics, error) {
879+
func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.MetricRequest) (*dto.Metrics, error) {
810880
from, to, err := utils2.ParseDateRange(request.From, request.To)
811881
if err != nil {
812882
return nil, err
@@ -819,10 +889,7 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
819889
}
820890

821891
if len(releases) == 0 {
822-
return &DoraMetrics{
823-
AppId: request.AppId,
824-
EnvId: request.EnvId,
825-
}, nil
892+
return utils2.CreateEmptyMetrics(), nil
826893
}
827894

828895
var releaseIds []int
@@ -842,66 +909,19 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
842909
return nil, err
843910
}
844911

845-
return impl.CalculateDoraMetrics(request.AppId, request.EnvId, releases, materials, leadTimes, from, to), nil
846-
}
912+
// Get previous release with bounds checking
913+
var lastRelease *sql.AppRelease
914+
if len(releases) > 0 {
915+
lastId := releases[len(releases)-1].Id
916+
lastRelease, err = impl.appReleaseRepository.GetPreviousRelease(request.AppId, request.EnvId, lastId)
917+
if err != nil && !utils.IsErrNoRows(err) {
918+
impl.logger.Errorw("error getting previous release from db ", "err", err)
919+
// Don't return error, just continue without previous release
920+
}
921+
if utils.IsErrNoRows(err) {
922+
lastRelease = nil
923+
}
924+
}
847925

848-
/*
849-
USAGE EXAMPLES:
850-
851-
1. Calculate DORA metrics for a single app-environment pair (includes materials data):
852-
doraMetrics, err := deploymentService.ProcessSingleDoraMetrics(&dto.MetricRequest{
853-
AppId: 123,
854-
EnvId: 456,
855-
From: "2024-01-01T00:00:00Z",
856-
To: "2024-01-31T23:59:59Z",
857-
})
858-
859-
2. Calculate DORA metrics for multiple app-environment pairs (bulk - optimized without materials):
860-
doraMetricsArray, err := deploymentService.ProcessBulkDoraMetrics(&dto.BulkMetricRequest{
861-
AppEnvPairs: []dto.AppEnvPair{
862-
{AppId: 123, EnvId: 456},
863-
{AppId: 789, EnvId: 101},
864-
},
865-
From: &fromTime,
866-
To: &toTime,
867-
})
868-
869-
3. Get DORA metrics with performance classification:
870-
summary := deploymentService.GetDoraMetricsSummary(doraMetrics)
871-
872-
4. Use within existing getBulkDeploymentMetricsWithBulkQueries function:
873-
After fetching all releases and leadTimes (materials not needed for bulk), you can calculate DORA metrics:
874-
875-
doraMetricsArray := impl.CalculateDoraMetricsForBulk(request.AppEnvPairs, releasesByAppEnv, allLeadTimes, *request.From, *request.To)
876-
877-
// Access DORA metrics for each app-env pair:
878-
for _, doraMetrics := range doraMetricsArray {
879-
fmt.Printf("App-Env %d-%d: Deployment Frequency: %.2f/day, CFR: %.2f%%, Lead Time: %.2f min, MTTR: %.2f min\n",
880-
doraMetrics.AppId, doraMetrics.EnvId, doraMetrics.DeploymentFrequency, doraMetrics.ChangeFailureRate,
881-
doraMetrics.MeanLeadTimeForChanges, doraMetrics.MeanTimeToRecovery)
882-
}
883-
884-
DORA METRICS FORMULAS IMPLEMENTED:
885-
886-
1. Deployment Frequency: Successful Deployments ÷ Time Period (in days)
887-
- Measures how often successful deployments occur
888-
- Result: deployments per day
889-
890-
2. Change Failure Rate: (Failed Deployments ÷ Total Deployments) × 100
891-
- Measures percentage of deployments that cause failures
892-
- Result: percentage
893-
894-
3. Mean Lead Time for Changes: Σ(Deployment Time - Commit Time) ÷ Number of Changes
895-
- Measures average time from commit to successful deployment
896-
- Result: minutes
897-
898-
4. Mean Time to Recovery: Σ(Recovery Time - Failure Time) ÷ Number of Incidents
899-
- Measures average time to recover from failed deployments
900-
- Result: minutes
901-
902-
PERFORMANCE CLASSIFICATION:
903-
- Elite: Top performers (e.g., >1 deployment/day, <15% CFR, <1 day lead time, <1 hour MTTR)
904-
- High: High performers
905-
- Medium: Medium performers
906-
- Low: Low performers
907-
*/
926+
return impl.populateMetricsWithImprovedLogic(releases, materials, leadTimes, lastRelease, from, to)
927+
}

0 commit comments

Comments
 (0)