diff --git a/internal/measurement/measurement.go b/internal/measurement/measurement.go index 479235c0a6..d70e8e6019 100644 --- a/internal/measurement/measurement.go +++ b/internal/measurement/measurement.go @@ -21,6 +21,7 @@ import ( "slices" "strings" "time" + "unicode/utf8" "github.com/google/pprof/profile" ) @@ -209,7 +210,9 @@ func (ut UnitType) findByAlias(alias string) *Unit { // specified alias. It returns nil if the unit with such alias is not found. func (ut UnitType) sniffUnit(unit string) *Unit { unit = strings.ToLower(unit) - if len(unit) > 2 { + // Count runes rather than bytes so that multi-byte aliases such as "μs" + // are not mistaken for a plural form and stripped down to "μ". + if utf8.RuneCountInString(unit) > 2 { unit = strings.TrimSuffix(unit, "s") } return ut.findByAlias(unit) diff --git a/internal/measurement/measurement_test.go b/internal/measurement/measurement_test.go index 7521a64d26..b75d3b5a85 100644 --- a/internal/measurement/measurement_test.go +++ b/internal/measurement/measurement_test.go @@ -17,6 +17,8 @@ package measurement import ( "math" "testing" + + "github.com/google/pprof/profile" ) func TestScale(t *testing.T) { @@ -37,6 +39,10 @@ func TestScale(t *testing.T) { {2048, "mb", "auto", 2, "GB"}, {3.1536e7, "s", "auto", 8760, "hrs"}, {-1, "s", "ms", -1000, "ms"}, + {1, "μs", "ms", 0.001, "ms"}, + {2000, "μs", "auto", 2, "ms"}, + {1, "μS", "ns", 1000, "ns"}, + {1, "us", "μs", 1, "us"}, {1, "foo", "count", 1, ""}, {1, "foo", "bar", 1, "bar"}, {2000, "count", "count", 2000, ""}, @@ -74,3 +80,41 @@ func floatEqual(a, b float64) bool { avg := (math.Abs(a) + math.Abs(b)) / 2 return diff/avg < 0.0001 } + +func TestCommonValueType(t *testing.T) { + for _, tc := range []struct { + desc string + in []*profile.ValueType + wantUnit string + wantErr bool + }{ + { + desc: "microseconds and milliseconds are compatible", + in: []*profile.ValueType{{Type: "cpu", Unit: "ms"}, {Type: "cpu", Unit: "μs"}}, + wantUnit: "μs", + }, + { + desc: "microseconds and seconds are compatible", + in: []*profile.ValueType{{Type: "cpu", Unit: "μs"}, {Type: "cpu", Unit: "s"}}, + wantUnit: "μs", + }, + { + desc: "time and memory units are incompatible", + in: []*profile.ValueType{{Type: "cpu", Unit: "μs"}, {Type: "cpu", Unit: "kb"}}, + wantErr: true, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + got, err := CommonValueType(tc.in) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Fatalf("CommonValueType(%v) error = %v, want error presence %v", tc.in, err, tc.wantErr) + } + if tc.wantErr { + return + } + if got == nil || got.Unit != tc.wantUnit { + t.Errorf("CommonValueType(%v) = %v, want unit %q", tc.in, got, tc.wantUnit) + } + }) + } +}