Skip to content

Commit

Permalink
Share unit definitions between Go and Javascript. (#852)
Browse files Browse the repository at this point in the history
Go unit definitions (from measurement package) are now converted to
JSON and delivered to Javascript code so that we do not need to
duplicate these definitions in Javascript.

Had to make the unit type definitions exported with public fields.

Also had to change a test case to expect "us" instead of "µs".
  • Loading branch information
ghemawat authored Apr 24, 2024
1 parent 57759fc commit a892ee0
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 55 deletions.
1 change: 1 addition & 0 deletions internal/driver/html/stacks.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
{{template "script" .}}
{{template "stacks_js"}}
<script>
pprofUnitDefs = {{.UnitDefs}};
stackViewer({{.Stacks}}, {{.Nodes}});
</script>
</body>
Expand Down
33 changes: 11 additions & 22 deletions internal/driver/html/stacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,36 +575,25 @@ function stackViewer(stacks, nodes) {
}
}

// Mapping from unit to a list of display scales/labels.
// List should be ordered by increasing unit size.
const pprofUnits = new Map([
['B', [
['B', 1],
['kB', Math.pow(2, 10)],
['MB', Math.pow(2, 20)],
['GB', Math.pow(2, 30)],
['TB', Math.pow(2, 40)],
['PB', Math.pow(2, 50)]]],
['s', [
['ns', 1e-9],
['µs', 1e-6],
['ms', 1e-3],
['s', 1],
['hrs', 60*60]]]]);

// pprofUnitText returns a formatted string to display for value in the specified unit.
function pprofUnitText(value, unit) {
const sign = (value < 0) ? "-" : "";
let v = Math.abs(value);
// Rescale to appropriate display unit.
const list = pprofUnits.get(unit);
let list = null;
for (const def of pprofUnitDefs) {
if (def.DefaultUnit.CanonicalName == unit) {
list = def.Units;
v *= def.DefaultUnit.Factor;
break;
}
}
if (list) {
// Stop just before entry that is too large.
for (let i = 0; i < list.length; i++) {
if (i == list.length-1 || list[i+1][1] > v) {
const [name, scale] = list[i];
v /= scale;
unit = name;
if (i == list.length-1 || list[i+1].Factor > v) {
v /= list[i].Factor;
unit = list[i].CanonicalName;
break;
}
}
Expand Down
6 changes: 4 additions & 2 deletions internal/driver/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"html/template"
"net/http"

"github.com/google/pprof/internal/measurement"
"github.com/google/pprof/internal/report"
)

Expand Down Expand Up @@ -52,7 +53,8 @@ func (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) {

_, legend := report.TextItems(rpt)
ui.render(w, req, "stacks", rpt, errList, legend, webArgs{
Stacks: template.JS(b),
Nodes: nodes,
Stacks: template.JS(b),
Nodes: nodes,
UnitDefs: measurement.UnitTypes,
})
}
2 changes: 1 addition & 1 deletion internal/driver/testdata/testflame.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function TestFlame() {
// Time units, plus logic tests.
checkUnitText("s", 0.51e-9, "0.51ns");
checkUnitText("s", 3e-9, "3ns");
checkUnitText("s", 1.23e-6, "1.23µs");
checkUnitText("s", 1.23e-6, "1.23us");
checkUnitText("s", 0.04, "40ms");
checkUnitText("s", 1, "1s");
checkUnitText("s", 3599, "3599s");
Expand Down
2 changes: 2 additions & 0 deletions internal/driver/webui.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"time"

"github.com/google/pprof/internal/graph"
"github.com/google/pprof/internal/measurement"
"github.com/google/pprof/internal/plugin"
"github.com/google/pprof/internal/report"
"github.com/google/pprof/profile"
Expand Down Expand Up @@ -88,6 +89,7 @@ type webArgs struct {
FlameGraph template.JS
Stacks template.JS
Configs []configMenuEntry
UnitDefs []measurement.UnitType
}

func serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {
Expand Down
61 changes: 31 additions & 30 deletions internal/measurement/measurement.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
if v1.Unit == v2.Unit {
return true
}
for _, ut := range unitTypes {
for _, ut := range UnitTypes {
if ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil {
return true
}
Expand All @@ -130,7 +130,7 @@ func Scale(value int64, fromUnit, toUnit string) (float64, string) {
v, u := Scale(-value, fromUnit, toUnit)
return -v, u
}
for _, ut := range unitTypes {
for _, ut := range UnitTypes {
if v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok {
return v, u
}
Expand Down Expand Up @@ -177,26 +177,26 @@ func Percentage(value, total int64) string {
}
}

// unit includes a list of aliases representing a specific unit and a factor
// Unit includes a list of aliases representing a specific unit and a factor
// which one can multiple a value in the specified unit by to get the value
// in terms of the base unit.
type unit struct {
canonicalName string
type Unit struct {
CanonicalName string
aliases []string
factor float64
Factor float64
}

// unitType includes a list of units that are within the same category (i.e.
// UnitType includes a list of units that are within the same category (i.e.
// memory or time units) and a default unit to use for this type of unit.
type unitType struct {
defaultUnit unit
units []unit
type UnitType struct {
DefaultUnit Unit
Units []Unit
}

// findByAlias returns the unit associated with the specified alias. It returns
// nil if the unit with such alias is not found.
func (ut unitType) findByAlias(alias string) *unit {
for _, u := range ut.units {
func (ut UnitType) findByAlias(alias string) *Unit {
for _, u := range ut.Units {
for _, a := range u.aliases {
if alias == a {
return &u
Expand All @@ -208,7 +208,7 @@ func (ut unitType) findByAlias(alias string) *unit {

// sniffUnit simpifies the input alias and returns the unit associated with the
// specified alias. It returns nil if the unit with such alias is not found.
func (ut unitType) sniffUnit(unit string) *unit {
func (ut UnitType) sniffUnit(unit string) *Unit {
unit = strings.ToLower(unit)
if len(unit) > 2 {
unit = strings.TrimSuffix(unit, "s")
Expand All @@ -219,13 +219,13 @@ func (ut unitType) sniffUnit(unit string) *unit {
// autoScale takes in the value with units of the base unit and returns
// that value scaled to a reasonable unit if a reasonable unit is
// found.
func (ut unitType) autoScale(value float64) (float64, string, bool) {
func (ut UnitType) autoScale(value float64) (float64, string, bool) {
var f float64
var unit string
for _, u := range ut.units {
if u.factor >= f && (value/u.factor) >= 1.0 {
f = u.factor
unit = u.canonicalName
for _, u := range ut.Units {
if u.Factor >= f && (value/u.Factor) >= 1.0 {
f = u.Factor
unit = u.CanonicalName
}
}
if f == 0 {
Expand All @@ -239,46 +239,47 @@ func (ut unitType) autoScale(value float64) (float64, string, bool) {
// included in the unitType, then a false boolean will be returned. If the
// toUnit is not in the unitType, the value will be returned in terms of the
// default unitType.
func (ut unitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
func (ut UnitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {
fromUnit := ut.sniffUnit(fromUnitStr)
if fromUnit == nil {
return 0, "", false
}
v := float64(value) * fromUnit.factor
v := float64(value) * fromUnit.Factor
if toUnitStr == "minimum" || toUnitStr == "auto" {
if v, u, ok := ut.autoScale(v); ok {
return v, u, true
}
return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
}
toUnit := ut.sniffUnit(toUnitStr)
if toUnit == nil {
return v / ut.defaultUnit.factor, ut.defaultUnit.canonicalName, true
return v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true
}
return v / toUnit.factor, toUnit.canonicalName, true
return v / toUnit.Factor, toUnit.CanonicalName, true
}

var unitTypes = []unitType{{
units: []unit{
// UnitTypes holds the definition of units known to pprof.
var UnitTypes = []UnitType{{
Units: []Unit{
{"B", []string{"b", "byte"}, 1},
{"kB", []string{"kb", "kbyte", "kilobyte"}, float64(1 << 10)},
{"MB", []string{"mb", "mbyte", "megabyte"}, float64(1 << 20)},
{"GB", []string{"gb", "gbyte", "gigabyte"}, float64(1 << 30)},
{"TB", []string{"tb", "tbyte", "terabyte"}, float64(1 << 40)},
{"PB", []string{"pb", "pbyte", "petabyte"}, float64(1 << 50)},
},
defaultUnit: unit{"B", []string{"b", "byte"}, 1},
DefaultUnit: Unit{"B", []string{"b", "byte"}, 1},
}, {
units: []unit{
Units: []Unit{
{"ns", []string{"ns", "nanosecond"}, float64(time.Nanosecond)},
{"us", []string{"μs", "us", "microsecond"}, float64(time.Microsecond)},
{"ms", []string{"ms", "millisecond"}, float64(time.Millisecond)},
{"s", []string{"s", "sec", "second"}, float64(time.Second)},
{"hrs", []string{"hour", "hr"}, float64(time.Hour)},
},
defaultUnit: unit{"s", []string{}, float64(time.Second)},
DefaultUnit: Unit{"s", []string{}, float64(time.Second)},
}, {
units: []unit{
Units: []Unit{
{"n*GCU", []string{"nanogcu"}, 1e-9},
{"u*GCU", []string{"microgcu"}, 1e-6},
{"m*GCU", []string{"milligcu"}, 1e-3},
Expand All @@ -289,5 +290,5 @@ var unitTypes = []unitType{{
{"T*GCU", []string{"teragcu"}, 1e12},
{"P*GCU", []string{"petagcu"}, 1e15},
},
defaultUnit: unit{"GCU", []string{}, 1.0},
DefaultUnit: Unit{"GCU", []string{}, 1.0},
}}

0 comments on commit a892ee0

Please sign in to comment.