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  }