Skip to content
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

feat(kaze/controllers): Implement Server sent events at regular speci… #187

Merged
merged 12 commits into from
Apr 7, 2020

Conversation

karan0299
Copy link
Contributor

…fied period for metrics

services/kaze/controllers/application.go Outdated Show resolved Hide resolved
time.Sleep(2 * configs.ServiceConfig.Mizu.MetricsInterval)
}()
c.Stream(func(w io.Writer) bool {
if _, ok := <-chanStream; ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the chanStream part necessary?

You can just include the time.Sleep part in this function itself

go func() {
defer close(chanStream)
chanStream <- true
time.Sleep(2 * configs.ServiceConfig.Mizu.MetricsInterval)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the 2 * part is not required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 * part was due to you reply to the question I asked in slack for time interval between two Server Sent events

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, it should be just configs.ServiceConfig.Mizu.MetricsInterval

c.SSEvent("metrics", metrics)
return true
}
return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the overall function will be simply

	c.Stream(func(w io.Writer) bool {
			metrics := mongo.FetchContainerMetrics(types.M{
				mongo.NameKey: appName,
				mongo.TimestampKey: types.M{
					"$gte": time.Now().Unix() - configs.ServiceConfig.Mizu.MetricsInterval * time.Second,
				},
			})
			c.SSEvent("metrics", metrics)
                        time.Sleep(configs.ServiceConfig.Mizu.MetricsInterval * time.Second)
			return true
		}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is using a channel any different than the above function ^ ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, as was discussed over slack, there was unknown i/o timeout error. I still am not sure about the exact reason but here what I infer from the success:

  • The original time.Sleep() was forcing the stream to halt somehow, but SSEvent was not prepared for discrete streams.
  • So by using this channel mutex triggered SSEvent, it is suppressed during the halt duration. And hence no timeout errors. I tried to mimic some of the errors by manipulating the goroutine and this inference feels rational.
  • gin's SSEvent implementation is buggy and we might need a replacement later.

@alphadose
Copy link
Member

After doing the requested changes test your feature using this example https://www.w3schools.com/html/html5_serversentevents.asp

Also keep a track of the requests in the network tab

"$gte": time.Now().Unix() - timeSpan,
},
})
count, _ := strconv.ParseInt(metricsCount, 10, 64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to check for error handling here. If someone sends a string "meow" in the "fetch_count" query param then the above line will throw an error

},
}, count)
c.SSEvent("metrics", metrics)
fetchInt, _ := strconv.ParseInt(metricsFetchInterval, 10, 64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for error handling here too in case "metricsFetchInterval" is a string like "meow"

Comment on lines 235 to 236
metricsFetchInterval := c.Query("fetch_interval")
metricsCount := c.Query("fetch_count")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case these query params are not set, you should have some default values in place.

@alphadose
Copy link
Member

Also, fix your commit message as per the CONTRIBUTING.md guidelines

if fetchIntTime < configs.ServiceConfig.Mizu.MetricsInterval {
fetchIntTime = 2 * fetchIntTime
}
time.Sleep(configs.ServiceConfig.Mizu.MetricsInterval * time.Second * fetchIntTime)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to multiply configs.ServiceConfig.Mizu.MetricsInterval and fetchIntTime in this context ?

Comment on lines 258 to 259
if fetchIntTime < configs.ServiceConfig.Mizu.MetricsInterval {
fetchIntTime = 2 * fetchIntTime
Copy link
Member

@alphadose alphadose Feb 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check for this condition outside c.Stream.
Also to retain safety you can change fetchIntTime = 2 * fetchIntTime to fetchIntTime = 2 * configs.ServiceConfig.Mizu.MetricsInterval

Comment on lines 236 to 237
metricsFetchInterval := c.Query("fetch_interval")
fetchInt, err := strconv.ParseInt(metricsFetchInterval, 10, 64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the line metricsFetchInterval := c.Query("fetch_interval") is unnecessary
You can just do fetchInt, err := strconv.ParseInt(c.Query("fetch_interval"), 10, 64)

Doing this might prevent an extra register being used unnecessarily in case the compiler's assembly optimization was not good enough

Same for the "fetch_counts" part

@@ -228,29 +230,36 @@ func TransferApplicationOwnership(c *gin.Context) {

// FetchMetrics retrieves the metrics of an application's container
func FetchMetrics(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/event-stream")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is unnecessary as the response headers are automatically set during c.SSEvent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supra08 fix this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@alphadose
Copy link
Member

Also you can rename "fetch_interval" and "fetch_count" to just "interval" and "count" respectively. That way it is more readable and easier to use from the perspective of an API user.

c.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "App name not provided for metrics",
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a return statement is required after this to terminate the function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you can leave the check of appName == "".

This is already handled by the IsAppOwner middleware. You cannot create an app with an empty string as name. This rule is already enforced.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supra08 remove the check for appName == ""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -228,29 +230,36 @@ func TransferApplicationOwnership(c *gin.Context) {

// FetchMetrics retrieves the metrics of an application's container
func FetchMetrics(c *gin.Context) {
c.Writer.Header().Set("Content-Type", "text/event-stream")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supra08 fix this

c.AbortWithStatusJSON(400, gin.H{
"success": false,
"error": "App name not provided for metrics",
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@supra08 remove the check for appName == ""

c.SSEvent("metrics", metrics)
return true
}
return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is using a channel any different than the above function ^ ?

@supra08
Copy link
Member

supra08 commented Apr 5, 2020

@alphadose

@codecov-io
Copy link

Codecov Report

❗ No coverage uploaded for pull request base (develop@7135437). Click here to learn what that means.
The diff coverage is n/a.

@alphadose alphadose merged commit 1b12e4c into develop Apr 7, 2020
@alphadose alphadose deleted the implement-SSE branch April 7, 2020 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants