Skip to content

Commit c4d020d

Browse files
Merge pull request #348 from devtron-labs/single-dora-metrics
misc: update DORA metrics calculation logic for single application
2 parents 3aafabf + 6369edd commit c4d020d

2 files changed

Lines changed: 83 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: 82 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,72 @@ 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+
lastFailedTime := ""
833+
recoveryTimeLastFailed := float64(0)
834+
for i := 0; i < len(releases); i++ {
835+
if releases[i].ReleaseStatus == dto.Failure {
836+
if lastFailedTime == "" {
837+
lastFailedTime = releases[i].ReleaseTime.Format(constants.Layout)
838+
}
839+
if i < len(releases)-1 && releases[i+1].ReleaseStatus == dto.Failure {
840+
continue
841+
}
842+
for j := i - 1; j >= 0; j-- {
843+
if releases[j].ReleaseStatus == dto.Success {
844+
releases[i].RecoveryTime = releases[j].ReleaseTime.Sub(releases[i].ReleaseTime).Minutes()
845+
if recoveryTimeLastFailed == 0 {
846+
recoveryTimeLastFailed = releases[i].RecoveryTime
847+
}
848+
break
849+
}
850+
}
851+
}
852+
}
853+
854+
metrics := &dto.Metrics{
855+
Series: releases,
856+
AverageCycleTime: impl.calculateDeploymentFrequency(releases, fromTime, toTime),
857+
AverageLeadTime: impl.calculateMeanLeadTimeForChanges(releases),
858+
ChangeFailureRate: impl.calculateChangeFailureRateNew(releases),
859+
AverageRecoveryTime: impl.calculateMeanTimeToRecovery(releases),
860+
LastFailedTime: lastFailedTime,
861+
RecoveryTimeLastFailed: recoveryTimeLastFailed,
862+
}
863+
864+
// Calculate change size metrics
865+
if len(metrics.Series) > 0 {
866+
impl.calculateChangeSize(metrics)
867+
}
868+
869+
return metrics, nil
870+
}
871+
808872
// ProcessSingleDoraMetrics processes DORA metrics for a single app-env pair
809-
func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.MetricRequest) (*DoraMetrics, error) {
873+
func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.MetricRequest) (*dto.Metrics, error) {
810874
from, to, err := utils2.ParseDateRange(request.From, request.To)
811875
if err != nil {
812876
return nil, err
@@ -819,10 +883,7 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
819883
}
820884

821885
if len(releases) == 0 {
822-
return &DoraMetrics{
823-
AppId: request.AppId,
824-
EnvId: request.EnvId,
825-
}, nil
886+
return utils2.CreateEmptyMetrics(), nil
826887
}
827888

828889
var releaseIds []int
@@ -842,66 +903,19 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
842903
return nil, err
843904
}
844905

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

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-
*/
920+
return impl.populateMetricsWithImprovedLogic(releases, materials, leadTimes, lastRelease, from, to)
921+
}

0 commit comments

Comments
 (0)