From aec52c6402bcb4f2fa6386bb4bd1c4a03d96eda4 Mon Sep 17 00:00:00 2001 From: Herbert Date: Thu, 30 Jul 2026 18:33:36 +0800 Subject: [PATCH] cache actortype label in CommonLabels CommonLabels computed the actor type label on every metric emission via fmt.Sprintf("%T", ...) followed by strings.Replace, incurring reflection and string allocations on a hot path. Since the label value is constant per concrete actor type, cache it in a sync.Map keyed by reflect.Type and compute it only once per type. Use reflect.TypeOf(...).String() in place of fmt.Sprintf to produce the same output, and guard against a nil actor to preserve the previous "" behavior. --- actor/metrics.go | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/actor/metrics.go b/actor/metrics.go index bca3a6b8c..4276cb145 100644 --- a/actor/metrics.go +++ b/actor/metrics.go @@ -3,8 +3,9 @@ package actor import ( - "fmt" + "reflect" "strings" + "sync" "github.com/asynkron/protoactor-go/extensions" "github.com/asynkron/protoactor-go/metrics" @@ -64,10 +65,37 @@ func SystemLabels(system *ActorSystem) []attribute.KeyValue { } } +// actorTypeLabels caches the "actortype" attribute keyed by the actor's concrete +// reflect.Type, avoiding the repeated reflection/formatting cost of computing the +// type name on every metric emission. +var actorTypeLabels sync.Map // map[reflect.Type]attribute.KeyValue + +// actorTypeLabel returns a cached "actortype" label for the given actor, computing +// (and caching) it on first use for each concrete type. +func actorTypeLabel(actor Actor) attribute.KeyValue { + t := reflect.TypeOf(actor) + if t == nil { + return attribute.String("actortype", "") + } + if v, ok := actorTypeLabels.Load(t); ok { + return v.(attribute.KeyValue) + } + + // Match the previous fmt.Sprintf("%T", ...) output, stripping a leading '*' + // for pointer types. + name := t.String() + if t.Kind() == reflect.Ptr { + name = strings.Replace(name, "*", "", 1) + } + + kv := attribute.String("actortype", name) + actorTypeLabels.Store(t, kv) + + return kv +} + // CommonLabels returns the default set of labels for an actor metric, including // system-wide labels and the specific actor type. func (m *Metrics) CommonLabels(ctx Context) []attribute.KeyValue { - return append(SystemLabels(ctx.ActorSystem()), - attribute.String("actortype", strings.Replace(fmt.Sprintf("%T", ctx.Actor()), "*", "", 1)), - ) + return append(SystemLabels(ctx.ActorSystem()), actorTypeLabel(ctx.Actor())) }