blob: f2da1451607ba86aaf82c5382ed1d6e572a04d27 [file] [log] [blame]
package metrics
import (
"app/context"
"fmt"
"io/ioutil"
"log"
"strings"
"syscall/zx"
"time"
"fidl/fuchsia/cobalt"
"fidl/fuchsia/mem"
)
const (
metricConfigPath = "/pkg/data/cobalt_config.pb"
releaseStage = cobalt.ReleaseStageDebug
)
var (
ctx *context.Context
logger cobalt.Logger
)
// Register uses an app context to connect to the cobalt service and configure the sw_delivery project
func Register(c *context.Context) {
ctx = c
ensureConnection()
}
type errCobalt cobalt.Status
func mapErrCobalt(status cobalt.Status) error {
if status == cobalt.StatusOk {
return nil
}
return errCobalt(status)
}
func (e errCobalt) Error() string {
switch cobalt.Status(e) {
case cobalt.StatusInvalidArguments:
return "cobalt: invalid arguments"
case cobalt.StatusEventTooBig:
return "cobalt: event too big"
case cobalt.StatusBufferFull:
return "cobalt: buffer full"
case cobalt.StatusInternalError:
return "cobalt: internal error"
}
return fmt.Sprintf("cobalt: unspecified error (%d)", e)
}
func newStringValue(name string, value string) cobalt.CustomEventValue {
var val cobalt.Value
val.SetStringValue(value)
return cobalt.CustomEventValue{
DimensionName: name,
Value: val,
}
}
func newIndexValue(name string, value uint32) cobalt.CustomEventValue {
var val cobalt.Value
val.SetIndexValue(value)
return cobalt.CustomEventValue{
DimensionName: name,
Value: val,
}
}
func newIntValue(name string, value int64) cobalt.CustomEventValue {
var val cobalt.Value
val.SetIntValue(value)
return cobalt.CustomEventValue{
DimensionName: name,
Value: val,
}
}
func newDoubleValue(name string, value float64) cobalt.CustomEventValue {
var val cobalt.Value
val.SetDoubleValue(value)
return cobalt.CustomEventValue{
DimensionName: name,
Value: val,
}
}
func connect() error {
config, err := ioutil.ReadFile(metricConfigPath)
if err != nil {
return fmt.Errorf("read cobalt config: %s", err)
}
configVmo, err := zx.NewVMO(uint64(len(config)), 0)
if err != nil {
return err
}
if err := configVmo.Write(config, 0); err != nil {
configVmo.Close()
return fmt.Errorf("write cobalt vmo: %s", err)
}
factoryRequest, factory, err := cobalt.NewLoggerFactoryInterfaceRequest()
if err != nil {
configVmo.Close()
return err
}
ctx.ConnectToEnvService(factoryRequest)
defer factory.Close()
loggerRequest, proxy, err := cobalt.NewLoggerInterfaceRequest()
if err != nil {
configVmo.Close()
return err
}
profile := cobalt.ProjectProfile{
Config: mem.Buffer{
Vmo: configVmo,
Size: uint64(len(config)),
},
ReleaseStage: releaseStage,
}
status, err := factory.CreateLogger(profile, loggerRequest)
if err != nil {
proxy.Close()
return err
} else if err := mapErrCobalt(status); err != nil {
proxy.Close()
return err
}
logger = proxy
return nil
}
func ensureConnection() bool {
if logger == nil {
if err := connect(); err != nil {
log.Printf("connect to cobalt: %s", err)
}
}
return logger != nil
}
func logString(metric metricID, value string) {
if !ensureConnection() {
return
}
status, err := logger.LogString(uint32(metric), value)
if err != nil {
log.Printf("logString: %s", err)
} else if err := mapErrCobalt(status); err != nil {
log.Printf("logString: %s", err)
}
}
func logElapsedTime(metric metricID, duration time.Duration, index uint32, component string) {
if !ensureConnection() {
return
}
durationInMicroseconds := duration.Nanoseconds() / time.Microsecond.Nanoseconds()
status, err := logger.LogElapsedTime(uint32(metric), index, component, durationInMicroseconds)
if err != nil {
log.Printf("logElapsedTime: %s", err)
} else if err := mapErrCobalt(status); err != nil {
log.Printf("logElapsedTime: %s", err)
}
}
func valueString(value cobalt.Value) (string, string) {
switch value.ValueTag {
case cobalt.ValueStringValue:
return "STRING", value.StringValue
case cobalt.ValueIntValue:
return "INT", fmt.Sprintf("%d", value.IntValue)
case cobalt.ValueDoubleValue:
return "DOUBLE", fmt.Sprintf("%f", value.DoubleValue)
case cobalt.ValueIndexValue:
return "INDEX", fmt.Sprintf("%d", value.IndexValue)
}
return "UNKNOWN", "UNKNOWN"
}
func logCustomEvent(metric metricID, parts []cobalt.CustomEventValue) {
if !ensureConnection() {
return
}
partStrings := make([]string, 0, len(parts))
for _, part := range parts {
t, v := valueString(part.Value)
partStrings = append(partStrings, fmt.Sprintf(" - %s: %s = %v", part.DimensionName, t, v))
}
log.Printf("logCustomEvent(%d)\n%s", uint32(metric), strings.Join(partStrings, "\n"))
status, err := logger.LogCustomEvent(uint32(metric), parts)
if err != nil {
log.Printf("logCustomEvent: %s", err)
} else if err := mapErrCobalt(status); err != nil {
log.Printf("logCustomEvent: %s", err)
}
}