pkg/analyzer/metrics.go (206 lines of code) (raw):

package analyzer import ( "fmt" "log/slog" "github.com/JetBrains/ij-perf-report-aggregator/pkg/model" "github.com/mcuadros/go-version" ) type Metric struct { Name string index int category int isRequired bool IsInstant bool sinceVersion string maxValue int } const appInitCategory = 1 var ( metricNameToDescriptor map[string]*Metric IjMetricDescriptors []*Metric ) func init() { index := 0 createMetric := func(name string) *Metric { result := &Metric{ Name: name, index: index, maxValue: 65535, } index++ IjMetricDescriptors = append(IjMetricDescriptors, result) return result } createVersionedUint16Metric := func(name string, sinceVersion string) *Metric { result := createMetric(name) result.sinceVersion = sinceVersion result.maxValue = 65535 return result } createRequiredMetric := func(name string) *Metric { result := createMetric(name) result.maxValue = 2147483647 result.isRequired = true return result } createUint32Metric := func(name string) *Metric { result := createMetric(name) result.maxValue = 4294967295 return result } createInt32Metric := func(name string) *Metric { result := createMetric(name) result.maxValue = 2147483647 return result } createUint16Metric := func(name string) *Metric { result := createMetric(name) result.maxValue = 65535 return result } createUint32RequiredMetric := func(name string) *Metric { result := createMetric(name) result.maxValue = 4294967295 result.isRequired = true return result } createUint16MetricWithCategory := func(name string, category int) *Metric { result := createMetric(name) result.category = category result.maxValue = 65535 return result } createInt32MetricWithCategory := func(name string, category int) *Metric { result := createMetric(name) result.maxValue = 2147483647 result.category = category return result } createInstantMetric := func(name string) *Metric { result := createMetric(name) result.IsInstant = true result.maxValue = 2147483647 return result } pluginDescriptorLoading := createUint16Metric("pluginDescriptorLoading_d") projectProfileLoading := createUint16MetricWithCategory("projectProfileLoading_d", appInitCategory) editorRestoring := createInt32Metric("editorRestoring") appComponentCreation := createUint16Metric("appComponentCreation_d") projectComponentCreation := createUint16Metric("projectComponentCreation_d") metricNameToDescriptor = map[string]*Metric{ "bootstrap": createUint32RequiredMetric("bootstrap_d"), "app initialization preparation": createRequiredMetric("appInitPreparation_d"), "app initialization": createRequiredMetric("appInit_d"), "plugin descriptor loading": pluginDescriptorLoading, // old name "plugin descriptors loading": pluginDescriptorLoading, "plugin initialization": createVersionedUint16Metric("pluginDescriptorInitV18_d", "18"), "app component creation": appComponentCreation, "app components creation": appComponentCreation, "project component creation": projectComponentCreation, "project components creation": projectComponentCreation, "project frame initialization": createUint16MetricWithCategory("projectFrameInit_d", appInitCategory), "project inspection profile loading": projectProfileLoading, // old name "project inspection profiles loading": projectProfileLoading, "project post-startup dumb-aware activities": createInt32Metric("projectDumbAware"), "editor restoring": editorRestoring, "editor restoring till paint": createInt32MetricWithCategory("editorRestoringTillPaint", appInitCategory), // old name "restoring editors": editorRestoring, // instant "splash initialization": createInstantMetric("splash_i"), "startUpCompleted": createInstantMetric("startUpCompleted"), "appStarter": createUint16Metric("appStarter_d"), // v19+ "eua showing": createVersionedUint16Metric("euaShowing_d", "19"), "service sync preloading": createUint32Metric("serviceSyncPreloading_d"), "service async preloading": createUint32Metric("serviceAsyncPreloading_d"), "project service sync preloading": createUint32Metric("projectServiceSyncPreloading_d"), "project service async preloading": createUint32Metric("projectServiceAsyncPreloading_d"), } } func ComputeIjMetrics(nonMetricFieldCount int, report *model.Report, result *[]any, logger *slog.Logger) error { for _, info := range IjMetricDescriptors { switch info.maxValue { case 65535: *result = append(*result, uint16(0)) case 4294967295: *result = append(*result, uint32(0)) case 2147483647: *result = append(*result, int32(-1)) default: *result = append(*result, -1) } } (*result)[nonMetricFieldCount+metricNameToDescriptor["startUpCompleted"].index] = int32(report.TotalDuration) for _, activity := range report.Activities { err := setMetric(nonMetricFieldCount, activity, report, result) if err != nil { return err } } for _, activity := range report.PrepareAppInitActivities { switch activity.Name { case "plugin descriptors loading": (*result)[nonMetricFieldCount+metricNameToDescriptor["plugin descriptor loading"].index] = uint16(activity.Duration) default: err := setMetric(nonMetricFieldCount, activity, report, result) if err != nil { return err } } } for _, activity := range report.TraceEvents { if activity.Phase == "i" && (activity.Name == "splash" || activity.Name == "splash shown") { (*result)[nonMetricFieldCount+metricNameToDescriptor["splash initialization"].index] = int32(activity.Timestamp / 1000) } } var notFoundMetrics []string for _, metric := range IjMetricDescriptors { if (*result)[nonMetricFieldCount+metric.index] != -1 { continue } if metric.isRequired { if metric.Name != "bootstrap_d" { logger.Error("metric is required, but not found, report will be skipped", "metric", metric.Name) return nil } } // undefined (*result)[nonMetricFieldCount+metric.index] = 0 if metric.sinceVersion != "" && version.Compare(report.Version, metric.sinceVersion, ">=") { notFoundMetrics = append(notFoundMetrics, metric.Name) } } if len(notFoundMetrics) > 0 { logger.Info("metrics not found", "name", notFoundMetrics) } return nil } func setMetric(nonMetricFieldCount int, activity model.Activity, report *model.Report, result *[]any) error { info, ok := metricNameToDescriptor[activity.Name] if !ok { return nil } if info.sinceVersion != "" && version.Compare(report.Version, info.sinceVersion, "<") { return nil } var v int if info.IsInstant { v = activity.Start } else { v = activity.Duration if v > info.maxValue { return fmt.Errorf("value outside of 0-%d range (generatedTime=%s, value=%v, activity=%s)", info.maxValue, report.Generated, v, activity.Name) } } if v < 0 { return fmt.Errorf("value must be positive (generatedTime: %s, value: %v)", report.Generated, v) } switch info.maxValue { case 65535: (*result)[nonMetricFieldCount+info.index] = uint16(v) case 4294967295: (*result)[nonMetricFieldCount+info.index] = uint32(v) case 2147483647: (*result)[nonMetricFieldCount+info.index] = int32(v) default: (*result)[nonMetricFieldCount+info.index] = v } return nil }