github.com/Financial-Times/publish-availability-monitor@v1.12.0/checks/scheduler.go (about) 1 package checks 2 3 import ( 4 "net/url" 5 "regexp" 6 "time" 7 8 "github.com/Financial-Times/go-logger/v2" 9 "github.com/Financial-Times/publish-availability-monitor/config" 10 "github.com/Financial-Times/publish-availability-monitor/content" 11 "github.com/Financial-Times/publish-availability-monitor/envs" 12 "github.com/Financial-Times/publish-availability-monitor/metrics" 13 ) 14 15 var ( 16 AbsoluteURLRegex = regexp.MustCompile("(?i)https?://.*") 17 ) 18 19 type SchedulerParam struct { 20 contentToCheck content.Content 21 publishDate time.Time 22 tid string 23 isMarkedDeleted bool 24 metricContainer *metrics.History 25 environments *envs.Environments 26 } 27 28 //nolint:gocognit 29 func ScheduleChecks( 30 p *SchedulerParam, 31 endpointSpecificChecks map[string]EndpointSpecificCheck, 32 appConfig *config.AppConfig, 33 metricSink chan metrics.PublishMetric, 34 e2eTestUUIDs []string, 35 log *logger.UPPLogger, 36 ) { 37 isE2ETest := config.IsE2ETestTransactionID(p.tid, e2eTestUUIDs) 38 39 for _, metric := range appConfig.MetricConf { 40 if !strSliceContains(metric.ContentTypes, p.contentToCheck.GetType()) && !isE2ETest { 41 continue 42 } 43 44 var capability *config.Capability 45 if isE2ETest { 46 capability = appConfig.GetCapability(metric.Alias) 47 if capability == nil { 48 continue 49 } 50 } 51 52 if p.environments.Len() > 0 { 53 for _, name := range p.environments.Names() { 54 env := p.environments.Environment(name) 55 var endpointURL *url.URL 56 var err error 57 58 if AbsoluteURLRegex.MatchString(metric.Endpoint) { 59 endpointURL, err = url.Parse(metric.Endpoint) 60 } else { 61 endpointURL, err = url.Parse(env.ReadURL + metric.Endpoint) 62 } 63 64 if err != nil { 65 log.WithError(err).Errorf("Cannot parse url [%v]", metric.Endpoint) 66 continue 67 } 68 69 var publishMetric = metrics.PublishMetric{ 70 UUID: p.contentToCheck.GetUUID(), 71 PublishOK: false, 72 PublishDate: p.publishDate, 73 Platform: name, 74 PublishInterval: metrics.Interval{}, 75 Config: metric, 76 Endpoint: *endpointURL, 77 TID: p.tid, 78 IsMarkedDeleted: p.isMarkedDeleted, 79 Capability: capability, 80 } 81 82 var checkInterval = appConfig.Threshold / metric.Granularity 83 var publishCheck = NewPublishCheck( 84 publishMetric, 85 env.Username, 86 env.Password, 87 appConfig.Threshold, 88 checkInterval, 89 metricSink, 90 endpointSpecificChecks, 91 log, 92 ) 93 go scheduleCheck(*publishCheck, p.metricContainer) 94 } 95 } else { 96 // generate a generic failure metric so that the absence of monitoring is logged 97 var publishMetric = metrics.PublishMetric{ 98 UUID: p.contentToCheck.GetUUID(), 99 PublishOK: false, 100 PublishDate: p.publishDate, 101 Platform: "none", 102 PublishInterval: metrics.Interval{}, 103 Config: metric, 104 Endpoint: url.URL{}, 105 TID: p.tid, 106 IsMarkedDeleted: p.isMarkedDeleted, 107 Capability: capability, 108 } 109 metricSink <- publishMetric 110 p.metricContainer.Update(publishMetric) 111 } 112 } 113 } 114 115 func scheduleCheck(check PublishCheck, metricContainer *metrics.History) { 116 //the date the SLA expires for this publish event 117 publishSLA := check.Metric.PublishDate.Add(time.Duration(check.Threshold) * time.Second) 118 119 //compute the actual seconds left until the SLA to compensate for the 120 //time passed between publish and the message reaching this point 121 secondsUntilSLA := time.Until(publishSLA).Seconds() 122 check.log.Infof("Checking %s. [%v] seconds until SLA.", 123 LoggingContextForCheck(check.Metric.Config.Alias, 124 check.Metric.UUID, 125 check.Metric.Platform, 126 check.Metric.TID), 127 int(secondsUntilSLA)) 128 129 //used to signal the ticker to stop after the threshold duration is reached 130 quitChan := make(chan bool) 131 go func() { 132 <-time.After(time.Duration(secondsUntilSLA) * time.Second) 133 close(quitChan) 134 }() 135 136 secondsSincePublish := time.Since(check.Metric.PublishDate).Seconds() 137 check.log.Infof("Checking %s. [%v] seconds elapsed since publish.", 138 LoggingContextForCheck(check.Metric.Config.Alias, 139 check.Metric.UUID, 140 check.Metric.Platform, 141 check.Metric.TID), 142 int(secondsSincePublish)) 143 144 elapsedIntervals := secondsSincePublish / float64(check.CheckInterval) 145 check.log.Infof("Checking %s. Skipping first [%v] checks", 146 LoggingContextForCheck(check.Metric.Config.Alias, 147 check.Metric.UUID, 148 check.Metric.Platform, 149 check.Metric.TID), 150 int(elapsedIntervals)) 151 152 checkNr := int(elapsedIntervals) + 1 153 // ticker to fire once per interval 154 tickerChan := time.NewTicker(time.Duration(check.CheckInterval) * time.Second) 155 for { 156 checkSuccessful, ignoreCheck := check.DoCheck() 157 if ignoreCheck { 158 check.log.Infof("Ignore check for %s", 159 LoggingContextForCheck(check.Metric.Config.Alias, 160 check.Metric.UUID, 161 check.Metric.Platform, 162 check.Metric.TID)) 163 tickerChan.Stop() 164 return 165 } 166 167 lower := (checkNr - 1) * check.CheckInterval 168 upper := checkNr * check.CheckInterval 169 check.Metric.PublishInterval = metrics.Interval{ 170 LowerBound: lower, 171 UpperBound: upper, 172 } 173 174 if checkSuccessful { 175 tickerChan.Stop() 176 check.Metric.PublishOK = true 177 178 check.ResultSink <- check.Metric 179 metricContainer.Update(check.Metric) 180 return 181 } 182 183 checkNr++ 184 185 select { 186 case <-tickerChan.C: 187 continue 188 case <-quitChan: 189 tickerChan.Stop() 190 //if we get here, checks were unsuccessful 191 check.Metric.PublishOK = false 192 check.ResultSink <- check.Metric 193 metricContainer.Update(check.Metric) 194 return 195 } 196 } 197 } 198 199 func strSliceContains(slice []string, str string) bool { 200 for _, s := range slice { 201 if s == str { 202 return true 203 } 204 } 205 return false 206 }