MM-61886: Add actionable page navigation metrics (#29332)

Page load is one of the metrics that we track and present
to MLT. However, in its current form, it is not very
actionable because it also contains the network latency.

We split the whole metric into these parts:
startTime
|
responseStart = TTFB
|
responseEnd = TTLB
|
domInteractive = Start of processing phase
|
loadEventEnd = Load complete

This gives us better visibility into exactly
which phase in the load process is slow.

I have experimented with other metrics like
- domContentLoadedEventStart
- domContentLoadedEventEnd
- domComplete

and observed that they do not have sufficient
gaps in the timespan to have any relevance.

Additionally, I have moved TTFB from being a
web vitals metric to being tracked from the performance
metrics to remain consistent with the other navigation
metrics measured.

Lastly, I took this chance to improve some of the
validation errors that we threw to include more
context into the input that was passed and why
does it fail.

This also meant that I had to change the tests
to check for error strings rather than direct
errors which is a bad thing, but I don't think
it's worth the effort trying to have named error
variables for all of them.

https://mattermost.atlassian.net/browse/MM-61886

```release-note
NONE
```
Этот коммит содержится в:
Agniva De Sarker
2024-11-29 11:24:35 +05:30
коммит произвёл GitHub
родитель b33622e32c
Коммит 4ec4b4d525
12 изменённых файлов: 138 добавлений и 36 удалений

Просмотреть файл

@@ -6,7 +6,6 @@ package model
import (
"fmt"
"strings"
"time"
"github.com/blang/semver/v4"
)
@@ -15,6 +14,9 @@ type MetricType string
const (
ClientTimeToFirstByte MetricType = "TTFB"
ClientTimeToLastByte MetricType = "TTLB"
ClientTimeToDOMInteractive MetricType = "dom_interactive"
ClientSplashScreenEnd MetricType = "splash_screen"
ClientFirstContentfulPaint MetricType = "FCP"
ClientLargestContentfulPaint MetricType = "LCP"
ClientInteractionToNextPaint MetricType = "INP"
@@ -54,7 +56,8 @@ var (
"modal_content",
"other",
)
AcceptedTrueFalseLabels = sliceToMapKey("true", "false")
AcceptedTrueFalseLabels = sliceToMapKey("true", "false")
AcceptedSplashScreenOrigins = sliceToMapKey("root", "team_controller")
)
type MetricSample struct {
@@ -86,7 +89,7 @@ func (r *PerformanceReport) IsValid() error {
reportVersion, err := semver.ParseTolerant(r.Version)
if err != nil {
return err
return fmt.Errorf("could not parse semver version: %s, %w", r.Version, err)
}
if reportVersion.Major != performanceReportVersion.Major || reportVersion.Minor > performanceReportVersion.Minor {
@@ -94,12 +97,12 @@ func (r *PerformanceReport) IsValid() error {
}
if r.Start > r.End {
return fmt.Errorf("report timestamps are erroneous")
return fmt.Errorf("report timestamps are erroneous: start_timestamp %f is greater than end_timestamp %f", r.Start, r.End)
}
now := time.Now().UnixMilli()
now := GetMillis()
if r.End < float64(now-performanceReportTTLMilliseconds) {
return fmt.Errorf("report is outdated: %f", r.End)
return fmt.Errorf("report is outdated: end_time %f is past %d ms from now", r.End, performanceReportTTLMilliseconds)
}
return nil

Просмотреть файл

@@ -4,7 +4,6 @@
package model
import (
"fmt"
"testing"
"time"
@@ -16,7 +15,7 @@ func TestPerformanceReport_IsValid(t *testing.T) {
tests := []struct {
name string
report *PerformanceReport
expected error
expected string
}{
{
name: "ValidReport",
@@ -26,12 +25,12 @@ func TestPerformanceReport_IsValid(t *testing.T) {
Start: float64(time.Now().UnixMilli() - 10000),
End: float64(time.Now().UnixMilli()),
},
expected: nil,
expected: "",
},
{
name: "NilReport",
report: nil,
expected: fmt.Errorf("the report is nil"),
expected: "the report is nil",
},
{
name: "UnsupportedVersion",
@@ -41,7 +40,7 @@ func TestPerformanceReport_IsValid(t *testing.T) {
Start: float64(time.Now().UnixMilli() - 10000),
End: float64(time.Now().UnixMilli()),
},
expected: fmt.Errorf("report version is not supported: server version: 0.1.0, report version: 2.0.0"),
expected: "report version is not supported:",
},
{
name: "ErroneousTimestamps",
@@ -51,7 +50,7 @@ func TestPerformanceReport_IsValid(t *testing.T) {
Start: float64(time.Now().UnixMilli()),
End: float64(time.Now().Add(-1 * time.Hour).UnixMilli()),
},
expected: fmt.Errorf("report timestamps are erroneous"),
expected: "report timestamps are erroneous",
},
{
name: "OutdatedReport",
@@ -61,15 +60,15 @@ func TestPerformanceReport_IsValid(t *testing.T) {
Start: float64(time.Now().Add(-7 * time.Minute).UnixMilli()),
End: float64(outdatedTimestamp),
},
expected: fmt.Errorf("report is outdated: %f", float64(outdatedTimestamp)),
expected: "report is outdated:",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.report.IsValid()
if tt.expected != nil {
require.EqualError(t, err, tt.expected.Error())
if tt.expected != "" {
require.Contains(t, err.Error(), tt.expected)
return
}
require.NoError(t, err)