Skip to content

Commit 0ecabd6

Browse files
authored
Merge pull request #28 from ca-srg/feature/local-metrics
feat: add local invocation metrics tracking with SQLite persistence
2 parents 89796b0 + 4c1f0b5 commit 0ecabd6

File tree

12 files changed

+969
-0
lines changed

12 files changed

+969
-0
lines changed

cmd/chat.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
appconfig "github.com/ca-srg/ragent/internal/config"
1717
"github.com/ca-srg/ragent/internal/embedding/bedrock"
18+
"github.com/ca-srg/ragent/internal/metrics"
1819
"github.com/ca-srg/ragent/internal/opensearch"
1920
"github.com/ca-srg/ragent/internal/search"
2021
"github.com/ca-srg/ragent/internal/slacksearch"
@@ -73,6 +74,7 @@ func init() {
7374
}
7475

7576
func runChat(cmd *cobra.Command, args []string) error {
77+
metrics.RecordInvocation(metrics.ModeChat)
7678
log.Println("Starting chat session...")
7779

7880
// Load configuration

cmd/mcp-server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
appcfg "github.com/ca-srg/ragent/internal/config"
2222
"github.com/ca-srg/ragent/internal/embedding/bedrock"
2323
"github.com/ca-srg/ragent/internal/mcpserver"
24+
"github.com/ca-srg/ragent/internal/metrics"
2425
"github.com/ca-srg/ragent/internal/observability"
2526
"github.com/ca-srg/ragent/internal/opensearch"
2627
"github.com/ca-srg/ragent/internal/slacksearch"
@@ -116,6 +117,8 @@ func init() {
116117
}
117118

118119
func runMCPServer(cmd *cobra.Command, args []string) error {
120+
metrics.RecordInvocation(metrics.ModeMCP)
121+
119122
// Load configuration
120123
cfg, err := appcfg.Load()
121124
if err != nil {
@@ -178,6 +181,9 @@ func runMCPServer(cmd *cobra.Command, args []string) error {
178181
if obsErr != nil {
179182
logger.Printf("observability initialization error: %v", obsErr)
180183
}
184+
if err := metrics.InitOTelMetrics(); err != nil {
185+
logger.Printf("metrics OTel initialization error: %v", err)
186+
}
181187
defer func() {
182188
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
183189
defer cancel()

cmd/query.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
appconfig "github.com/ca-srg/ragent/internal/config"
1515
"github.com/ca-srg/ragent/internal/embedding/bedrock"
16+
"github.com/ca-srg/ragent/internal/metrics"
1617
"github.com/ca-srg/ragent/internal/opensearch"
1718
"github.com/ca-srg/ragent/internal/slacksearch"
1819
commontypes "github.com/ca-srg/ragent/internal/types"
@@ -105,6 +106,8 @@ func init() {
105106
}
106107

107108
func runQuery(cmd *cobra.Command, args []string) error {
109+
metrics.RecordInvocation(metrics.ModeQuery)
110+
108111
// Handle --only-slack mode
109112
if queryOnlySlack {
110113
log.Printf("Starting slack-only search for: %s", queryText)

cmd/root.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package cmd
22

33
import (
4+
"log"
5+
46
"github.com/spf13/cobra"
7+
8+
"github.com/ca-srg/ragent/internal/metrics"
59
)
610

711
var rootCmd = &cobra.Command{
@@ -10,6 +14,16 @@ var rootCmd = &cobra.Command{
1014
Long: `RAGent is a CLI tool for building a RAG (Retrieval-Augmented Generation) system
1115
from Markdown documents using hybrid search capabilities (BM25 + vector search)
1216
with Amazon S3 Vectors and OpenSearch.`,
17+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
18+
if err := metrics.Init(); err != nil {
19+
log.Printf("Warning: metrics initialization failed: %v", err)
20+
}
21+
},
22+
PersistentPostRun: func(cmd *cobra.Command, args []string) {
23+
if err := metrics.Close(); err != nil {
24+
log.Printf("Warning: metrics close failed: %v", err)
25+
}
26+
},
1327
}
1428

1529
func Execute() error {

cmd/slack.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
appcfg "github.com/ca-srg/ragent/internal/config"
1717
"github.com/ca-srg/ragent/internal/embedding/bedrock"
18+
"github.com/ca-srg/ragent/internal/metrics"
1819
"github.com/ca-srg/ragent/internal/observability"
1920
"github.com/ca-srg/ragent/internal/slackbot"
2021
"github.com/ca-srg/ragent/internal/slacksearch"
@@ -30,6 +31,8 @@ var slackCmd = &cobra.Command{
3031
Use: "slack-bot",
3132
Short: "Start Slack Bot for RAG search",
3233
RunE: func(cmd *cobra.Command, args []string) error {
34+
metrics.RecordInvocation(metrics.ModeSlack)
35+
3336
// Load base config for search
3437
cfg, err := appcfg.Load()
3538
if err != nil {
@@ -42,6 +45,9 @@ var slackCmd = &cobra.Command{
4245
if obsErr != nil {
4346
logger.Printf("observability initialization error: %v", obsErr)
4447
}
48+
if err := metrics.InitOTelMetrics(); err != nil {
49+
logger.Printf("metrics OTel initialization error: %v", err)
50+
}
4551
defer func() {
4652
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
4753
defer cancel()

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ require (
3535
golang.org/x/time v0.14.0
3636
google.golang.org/api v0.258.0
3737
gopkg.in/yaml.v3 v3.0.1
38+
modernc.org/sqlite v1.42.2
3839
)
3940

4041
require (
@@ -56,6 +57,7 @@ require (
5657
github.com/aws/aws-sdk-go-v2/service/sts v1.38.1 // indirect
5758
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
5859
github.com/davecgh/go-spew v1.1.1 // indirect
60+
github.com/dustin/go-humanize v1.0.1 // indirect
5961
github.com/felixge/httpsnoop v1.0.4 // indirect
6062
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
6163
github.com/go-logr/logr v1.4.3 // indirect
@@ -67,7 +69,10 @@ require (
6769
github.com/gorilla/websocket v1.5.3 // indirect
6870
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
6971
github.com/inconshreveable/mousetrap v1.1.0 // indirect
72+
github.com/mattn/go-isatty v0.0.20 // indirect
73+
github.com/ncruces/go-strftime v0.1.9 // indirect
7074
github.com/pmezard/go-difflib v1.0.0 // indirect
75+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
7176
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
7277
github.com/spf13/pflag v1.0.7 // indirect
7378
github.com/stretchr/objx v0.5.2 // indirect
@@ -78,11 +83,15 @@ require (
7883
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
7984
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect
8085
golang.org/x/crypto v0.46.0 // indirect
86+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
8187
golang.org/x/net v0.48.0 // indirect
8288
golang.org/x/sys v0.39.0 // indirect
8389
golang.org/x/tools v0.39.0 // indirect
8490
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
8591
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
8692
google.golang.org/grpc v1.77.0 // indirect
8793
google.golang.org/protobuf v1.36.11 // indirect
94+
modernc.org/libc v1.66.10 // indirect
95+
modernc.org/mathutil v1.7.1 // indirect
96+
modernc.org/memory v1.11.0 // indirect
8897
)

go.sum

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmr
5555
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
5656
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5757
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
58+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
59+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
5860
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
5961
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
6062
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
@@ -74,6 +76,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
7476
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
7577
github.com/google/jsonschema-go v0.2.1-0.20250825175020-748c325cec76 h1:mBlBwtDebdDYr+zdop8N62a44g+Nbv7o2KjWyS1deR4=
7678
github.com/google/jsonschema-go v0.2.1-0.20250825175020-748c325cec76/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
79+
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
80+
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
7781
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
7882
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
7983
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -94,14 +98,20 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
9498
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
9599
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
96100
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
101+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
102+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
97103
github.com/modelcontextprotocol/go-sdk v0.4.0 h1:RJ6kFlneHqzTKPzlQqiunrz9nbudSZcYLmLHLsokfoU=
98104
github.com/modelcontextprotocol/go-sdk v0.4.0/go.mod h1:whv0wHnsTphwq7CTiKYHkLtwLC06WMoY2KpO+RB9yXQ=
105+
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
106+
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
99107
github.com/netflix/go-env v0.0.0-20220526054621-78278af1949d h1:SW84RkiEiaCfgTY3yRjPpIUeGVxd5Bs1Ezz2XX63jeM=
100108
github.com/netflix/go-env v0.0.0-20220526054621-78278af1949d/go.mod h1:sNUavIj8CuZI65dSVin9f1cioi7Siwne3KiLvJ/jsjg=
101109
github.com/opensearch-project/opensearch-go/v4 v4.5.0 h1:26XckmmF6MhlXt91Bu1yY6R51jy1Ns/C3XgIfvyeTRo=
102110
github.com/opensearch-project/opensearch-go/v4 v4.5.0/go.mod h1:VmFc7dqOEM3ZtLhrpleOzeq+cqUgNabqQG5gX0xId64=
103111
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
104112
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
113+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
114+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
105115
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
106116
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
107117
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -166,12 +176,17 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
166176
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
167177
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
168178
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
179+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
180+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
181+
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
182+
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
169183
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
170184
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
171185
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
172186
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
173187
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
174188
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
189+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
175190
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
176191
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
177192
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
@@ -201,3 +216,29 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY
201216
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
202217
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
203218
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
219+
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
220+
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
221+
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
222+
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
223+
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
224+
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
225+
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
226+
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
227+
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
228+
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
229+
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
230+
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
231+
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
232+
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
233+
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
234+
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
235+
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
236+
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
237+
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
238+
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
239+
modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74=
240+
modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8=
241+
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
242+
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
243+
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
244+
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

internal/metrics/counter.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package metrics
2+
3+
import (
4+
"log"
5+
"sync"
6+
)
7+
8+
var (
9+
globalStore *Store
10+
initOnce sync.Once
11+
initErr error
12+
)
13+
14+
// Init initializes the global metrics store.
15+
// This should be called once at application startup.
16+
// It is safe to call multiple times; subsequent calls are no-ops.
17+
func Init() error {
18+
initOnce.Do(func() {
19+
globalStore, initErr = NewStore()
20+
if initErr != nil {
21+
log.Printf("metrics: failed to initialize store: %v", initErr)
22+
}
23+
})
24+
return initErr
25+
}
26+
27+
// RecordInvocation increments the invocation count for the given mode.
28+
// If the store is not initialized, this is a no-op (logs a warning).
29+
func RecordInvocation(mode Mode) {
30+
if globalStore == nil {
31+
// Attempt lazy initialization
32+
if err := Init(); err != nil {
33+
log.Printf("metrics: cannot record invocation, store not initialized: %v", err)
34+
return
35+
}
36+
}
37+
38+
if err := globalStore.Increment(mode); err != nil {
39+
log.Printf("metrics: failed to record invocation for %s: %v", mode, err)
40+
}
41+
}
42+
43+
// GetStats returns the cumulative invocation counts for all modes.
44+
// Returns nil if the store is not initialized.
45+
func GetStats() map[Mode]int64 {
46+
if globalStore == nil {
47+
return nil
48+
}
49+
50+
stats, err := globalStore.GetAllTotals()
51+
if err != nil {
52+
log.Printf("metrics: failed to get stats: %v", err)
53+
return nil
54+
}
55+
56+
return stats
57+
}
58+
59+
// GetTotalForMode returns the cumulative count for a specific mode.
60+
// Returns 0 if the store is not initialized or on error.
61+
func GetTotalForMode(mode Mode) int64 {
62+
if globalStore == nil {
63+
return 0
64+
}
65+
66+
total, err := globalStore.GetTotalByMode(mode)
67+
if err != nil {
68+
log.Printf("metrics: failed to get total for %s: %v", mode, err)
69+
return 0
70+
}
71+
72+
return total
73+
}
74+
75+
// Close closes the global metrics store.
76+
// Should be called at application shutdown.
77+
func Close() error {
78+
if globalStore != nil {
79+
return globalStore.Close()
80+
}
81+
return nil
82+
}
83+
84+
// GetStore returns the global store instance.
85+
// This is primarily for testing purposes.
86+
func GetStore() *Store {
87+
return globalStore
88+
}
89+
90+
// SetStoreForTesting sets the global store instance for testing purposes.
91+
// This should only be used in tests.
92+
func SetStoreForTesting(store *Store) {
93+
globalStore = store
94+
}
95+
96+
// ResetForTesting resets the global state for testing purposes.
97+
// This should only be used in tests.
98+
func ResetForTesting() {
99+
if globalStore != nil {
100+
_ = globalStore.Close()
101+
}
102+
globalStore = nil
103+
initOnce = sync.Once{}
104+
initErr = nil
105+
}

0 commit comments

Comments
 (0)