github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logqlmodel/stats/context.go (about) 1 /* 2 Package stats provides primitives for recording metrics across the query path. 3 Statistics are passed through the query context. 4 To start a new query statistics context use: 5 6 statsCtx, ctx := stats.NewContext(ctx) 7 8 Then you can update statistics by mutating data by using: 9 10 statsCtx.Add...(1) 11 12 To get the statistic from the current context you can use: 13 14 statsCtx := stats.FromContext(ctx) 15 16 Finally to get a snapshot of the current query statistic use 17 18 statsCtx.Result(time.Since(start), queueTime, totalEntriesReturned) 19 20 */ 21 package stats 22 23 import ( 24 "context" 25 "sync" 26 "sync/atomic" //lint:ignore faillint we can't use go.uber.org/atomic with a protobuf struct without wrapping it. 27 "time" 28 29 "github.com/dustin/go-humanize" 30 "github.com/go-kit/log" 31 ) 32 33 type ( 34 ctxKeyType string 35 Component int64 36 ) 37 38 const ( 39 statsKey ctxKeyType = "stats" 40 ) 41 42 // Context is the statistics context. It is passed through the query path and accumulates statistics. 43 type Context struct { 44 querier Querier 45 ingester Ingester 46 caches Caches 47 48 // store is the store statistics collected across the query path 49 store Store 50 // result accumulates results for JoinResult. 51 result Result 52 53 mtx sync.Mutex 54 } 55 56 type CacheType string 57 58 const ( 59 ChunkCache CacheType = "chunk" //nolint:staticcheck 60 IndexCache = "index" 61 ResultCache = "result" 62 WriteDedupeCache = "write-dedupe" 63 ) 64 65 // NewContext creates a new statistics context 66 func NewContext(ctx context.Context) (*Context, context.Context) { 67 contextData := &Context{} 68 ctx = context.WithValue(ctx, statsKey, contextData) 69 return contextData, ctx 70 } 71 72 // FromContext returns the statistics context. 73 func FromContext(ctx context.Context) *Context { 74 v, ok := ctx.Value(statsKey).(*Context) 75 if !ok { 76 return &Context{} 77 } 78 return v 79 } 80 81 // Ingester returns the ingester statistics accumulated so far. 82 func (c *Context) Ingester() Ingester { 83 return Ingester{ 84 TotalReached: c.ingester.TotalReached, 85 TotalChunksMatched: c.ingester.TotalChunksMatched, 86 TotalBatches: c.ingester.TotalBatches, 87 TotalLinesSent: c.ingester.TotalLinesSent, 88 Store: c.store, 89 } 90 } 91 92 // Caches returns the cache statistics accumulated so far. 93 func (c *Context) Caches() Caches { 94 return Caches{ 95 Chunk: c.caches.Chunk, 96 Index: c.caches.Index, 97 Result: c.caches.Result, 98 } 99 } 100 101 // Reset clears the statistics. 102 func (c *Context) Reset() { 103 c.mtx.Lock() 104 defer c.mtx.Unlock() 105 106 c.store.Reset() 107 c.querier.Reset() 108 c.ingester.Reset() 109 c.result.Reset() 110 c.caches.Reset() 111 } 112 113 // Result calculates the summary based on store and ingester data. 114 func (c *Context) Result(execTime time.Duration, queueTime time.Duration, totalEntriesReturned int) Result { 115 r := c.result 116 117 r.Merge(Result{ 118 Querier: Querier{ 119 Store: c.store, 120 }, 121 Ingester: c.ingester, 122 Caches: c.caches, 123 }) 124 125 r.ComputeSummary(execTime, queueTime, totalEntriesReturned) 126 127 return r 128 } 129 130 // JoinResults merges a Result with the embedded Result in a context in a concurrency-safe manner. 131 func JoinResults(ctx context.Context, res Result) { 132 stats := FromContext(ctx) 133 stats.mtx.Lock() 134 defer stats.mtx.Unlock() 135 136 stats.result.Merge(res) 137 } 138 139 // JoinIngesterResult joins the ingester result statistics in a concurrency-safe manner. 140 func JoinIngesters(ctx context.Context, inc Ingester) { 141 stats := FromContext(ctx) 142 stats.mtx.Lock() 143 defer stats.mtx.Unlock() 144 145 stats.ingester.Merge(inc) 146 } 147 148 // ComputeSummary compute the summary of the statistics. 149 func (r *Result) ComputeSummary(execTime time.Duration, queueTime time.Duration, totalEntriesReturned int) { 150 r.Summary.TotalBytesProcessed = r.Querier.Store.Chunk.DecompressedBytes + r.Querier.Store.Chunk.HeadChunkBytes + 151 r.Ingester.Store.Chunk.DecompressedBytes + r.Ingester.Store.Chunk.HeadChunkBytes 152 r.Summary.TotalLinesProcessed = r.Querier.Store.Chunk.DecompressedLines + r.Querier.Store.Chunk.HeadChunkLines + 153 r.Ingester.Store.Chunk.DecompressedLines + r.Ingester.Store.Chunk.HeadChunkLines 154 r.Summary.ExecTime = execTime.Seconds() 155 if execTime != 0 { 156 r.Summary.BytesProcessedPerSecond = int64(float64(r.Summary.TotalBytesProcessed) / 157 execTime.Seconds()) 158 r.Summary.LinesProcessedPerSecond = int64(float64(r.Summary.TotalLinesProcessed) / 159 execTime.Seconds()) 160 } 161 if queueTime != 0 { 162 r.Summary.QueueTime = queueTime.Seconds() 163 } 164 165 r.Summary.TotalEntriesReturned = int64(totalEntriesReturned) 166 } 167 168 func (s *Store) Merge(m Store) { 169 s.TotalChunksRef += m.TotalChunksRef 170 s.TotalChunksDownloaded += m.TotalChunksDownloaded 171 s.ChunksDownloadTime += m.ChunksDownloadTime 172 s.Chunk.HeadChunkBytes += m.Chunk.HeadChunkBytes 173 s.Chunk.HeadChunkLines += m.Chunk.HeadChunkLines 174 s.Chunk.DecompressedBytes += m.Chunk.DecompressedBytes 175 s.Chunk.DecompressedLines += m.Chunk.DecompressedLines 176 s.Chunk.CompressedBytes += m.Chunk.CompressedBytes 177 s.Chunk.TotalDuplicates += m.Chunk.TotalDuplicates 178 } 179 180 func (q *Querier) Merge(m Querier) { 181 q.Store.Merge(m.Store) 182 } 183 184 func (i *Ingester) Merge(m Ingester) { 185 i.Store.Merge(m.Store) 186 i.TotalBatches += m.TotalBatches 187 i.TotalLinesSent += m.TotalLinesSent 188 i.TotalChunksMatched += m.TotalChunksMatched 189 i.TotalReached += m.TotalReached 190 } 191 192 func (c *Caches) Merge(m Caches) { 193 c.Chunk.Merge(m.Chunk) 194 c.Index.Merge(m.Index) 195 c.Result.Merge(m.Result) 196 } 197 198 func (c *Cache) Merge(m Cache) { 199 c.EntriesFound += m.EntriesFound 200 c.EntriesRequested += m.EntriesRequested 201 c.EntriesStored += m.EntriesStored 202 c.Requests += m.Requests 203 c.BytesSent += m.BytesSent 204 c.BytesReceived += m.BytesReceived 205 } 206 207 // Merge merges two results of statistics. 208 // This will increase the total number of Subqueries. 209 func (r *Result) Merge(m Result) { 210 r.Summary.Subqueries++ 211 r.Querier.Merge(m.Querier) 212 r.Ingester.Merge(m.Ingester) 213 r.Caches.Merge(m.Caches) 214 r.ComputeSummary(ConvertSecondsToNanoseconds(r.Summary.ExecTime+m.Summary.ExecTime), 215 ConvertSecondsToNanoseconds(r.Summary.QueueTime+m.Summary.QueueTime), int(r.Summary.TotalEntriesReturned)) 216 } 217 218 // ConvertSecondsToNanoseconds converts time.Duration representation of seconds (float64) 219 // into time.Duration representation of nanoseconds (int64) 220 func ConvertSecondsToNanoseconds(seconds float64) time.Duration { 221 return time.Duration(int64(seconds * float64(time.Second))) 222 } 223 224 func (r Result) ChunksDownloadTime() time.Duration { 225 return time.Duration(r.Querier.Store.ChunksDownloadTime + r.Ingester.Store.ChunksDownloadTime) 226 } 227 228 func (r Result) TotalDuplicates() int64 { 229 return r.Querier.Store.Chunk.TotalDuplicates + r.Ingester.Store.Chunk.TotalDuplicates 230 } 231 232 func (r Result) TotalChunksDownloaded() int64 { 233 return r.Querier.Store.TotalChunksDownloaded + r.Ingester.Store.TotalChunksDownloaded 234 } 235 236 func (r Result) TotalChunksRef() int64 { 237 return r.Querier.Store.TotalChunksRef + r.Ingester.Store.TotalChunksRef 238 } 239 240 func (r Result) TotalDecompressedBytes() int64 { 241 return r.Querier.Store.Chunk.DecompressedBytes + r.Ingester.Store.Chunk.DecompressedBytes 242 } 243 244 func (r Result) TotalDecompressedLines() int64 { 245 return r.Querier.Store.Chunk.DecompressedLines + r.Ingester.Store.Chunk.DecompressedLines 246 } 247 248 func (c *Context) AddIngesterBatch(size int64) { 249 atomic.AddInt64(&c.ingester.TotalBatches, 1) 250 atomic.AddInt64(&c.ingester.TotalLinesSent, size) 251 } 252 253 func (c *Context) AddIngesterTotalChunkMatched(i int64) { 254 atomic.AddInt64(&c.ingester.TotalChunksMatched, i) 255 } 256 257 func (c *Context) AddIngesterReached(i int32) { 258 atomic.AddInt32(&c.ingester.TotalReached, i) 259 } 260 261 func (c *Context) AddHeadChunkLines(i int64) { 262 atomic.AddInt64(&c.store.Chunk.HeadChunkLines, i) 263 } 264 265 func (c *Context) AddHeadChunkBytes(i int64) { 266 atomic.AddInt64(&c.store.Chunk.HeadChunkBytes, i) 267 } 268 269 func (c *Context) AddCompressedBytes(i int64) { 270 atomic.AddInt64(&c.store.Chunk.CompressedBytes, i) 271 } 272 273 func (c *Context) AddDecompressedBytes(i int64) { 274 atomic.AddInt64(&c.store.Chunk.DecompressedBytes, i) 275 } 276 277 func (c *Context) AddDecompressedLines(i int64) { 278 atomic.AddInt64(&c.store.Chunk.DecompressedLines, i) 279 } 280 281 func (c *Context) AddDuplicates(i int64) { 282 atomic.AddInt64(&c.store.Chunk.TotalDuplicates, i) 283 } 284 285 func (c *Context) AddChunksDownloadTime(i time.Duration) { 286 atomic.AddInt64(&c.store.ChunksDownloadTime, int64(i)) 287 } 288 289 func (c *Context) AddChunksDownloaded(i int64) { 290 atomic.AddInt64(&c.store.TotalChunksDownloaded, i) 291 } 292 293 func (c *Context) AddChunksRef(i int64) { 294 atomic.AddInt64(&c.store.TotalChunksRef, i) 295 } 296 297 // AddCacheEntriesFound counts the number of cache entries requested and found 298 func (c *Context) AddCacheEntriesFound(t CacheType, i int) { 299 stats := c.getCacheStatsByType(t) 300 if stats == nil { 301 return 302 } 303 304 atomic.AddInt32(&stats.EntriesFound, int32(i)) 305 } 306 307 // AddCacheEntriesRequested counts the number of keys requested from the cache 308 func (c *Context) AddCacheEntriesRequested(t CacheType, i int) { 309 stats := c.getCacheStatsByType(t) 310 if stats == nil { 311 return 312 } 313 314 atomic.AddInt32(&stats.EntriesRequested, int32(i)) 315 } 316 317 // AddCacheEntriesStored counts the number of keys *attempted* to be stored in the cache 318 // It should be noted that if a background writeback (https://grafana.com/docs/loki/latest/configuration/#cache_config) 319 // is configured we cannot know if these store attempts succeeded or not as this happens asynchronously 320 func (c *Context) AddCacheEntriesStored(t CacheType, i int) { 321 stats := c.getCacheStatsByType(t) 322 if stats == nil { 323 return 324 } 325 326 atomic.AddInt32(&stats.EntriesStored, int32(i)) 327 } 328 329 // AddCacheBytesRetrieved counts the amount of bytes retrieved from the cache 330 func (c *Context) AddCacheBytesRetrieved(t CacheType, i int) { 331 stats := c.getCacheStatsByType(t) 332 if stats == nil { 333 return 334 } 335 336 atomic.AddInt64(&stats.BytesReceived, int64(i)) 337 } 338 339 // AddCacheBytesSent counts the amount of bytes sent to the cache 340 // It should be noted that if a background writeback (https://grafana.com/docs/loki/latest/configuration/#cache_config) 341 // is configured we cannot know if these bytes actually got stored or not as this happens asynchronously 342 func (c *Context) AddCacheBytesSent(t CacheType, i int) { 343 stats := c.getCacheStatsByType(t) 344 if stats == nil { 345 return 346 } 347 348 atomic.AddInt64(&stats.BytesSent, int64(i)) 349 } 350 351 // AddCacheRequest counts the number of fetch/store requests to the cache 352 func (c *Context) AddCacheRequest(t CacheType, i int) { 353 stats := c.getCacheStatsByType(t) 354 if stats == nil { 355 return 356 } 357 358 atomic.AddInt32(&stats.Requests, int32(i)) 359 } 360 361 func (c *Context) getCacheStatsByType(t CacheType) *Cache { 362 var stats *Cache 363 switch t { 364 case ChunkCache: 365 stats = &c.caches.Chunk 366 case IndexCache: 367 stats = &c.caches.Index 368 case ResultCache: 369 stats = &c.caches.Result 370 default: 371 return nil 372 } 373 return stats 374 } 375 376 // Log logs a query statistics result. 377 func (r Result) Log(log log.Logger) { 378 _ = log.Log( 379 "Ingester.TotalReached", r.Ingester.TotalReached, 380 "Ingester.TotalChunksMatched", r.Ingester.TotalChunksMatched, 381 "Ingester.TotalBatches", r.Ingester.TotalBatches, 382 "Ingester.TotalLinesSent", r.Ingester.TotalLinesSent, 383 "Ingester.TotalChunksRef", r.Ingester.Store.TotalChunksRef, 384 "Ingester.TotalChunksDownloaded", r.Ingester.Store.TotalChunksDownloaded, 385 "Ingester.ChunksDownloadTime", time.Duration(r.Ingester.Store.ChunksDownloadTime), 386 "Ingester.HeadChunkBytes", humanize.Bytes(uint64(r.Ingester.Store.Chunk.HeadChunkBytes)), 387 "Ingester.HeadChunkLines", r.Ingester.Store.Chunk.HeadChunkLines, 388 "Ingester.DecompressedBytes", humanize.Bytes(uint64(r.Ingester.Store.Chunk.DecompressedBytes)), 389 "Ingester.DecompressedLines", r.Ingester.Store.Chunk.DecompressedLines, 390 "Ingester.CompressedBytes", humanize.Bytes(uint64(r.Ingester.Store.Chunk.CompressedBytes)), 391 "Ingester.TotalDuplicates", r.Ingester.Store.Chunk.TotalDuplicates, 392 393 "Querier.TotalChunksRef", r.Querier.Store.TotalChunksRef, 394 "Querier.TotalChunksDownloaded", r.Querier.Store.TotalChunksDownloaded, 395 "Querier.ChunksDownloadTime", time.Duration(r.Querier.Store.ChunksDownloadTime), 396 "Querier.HeadChunkBytes", humanize.Bytes(uint64(r.Querier.Store.Chunk.HeadChunkBytes)), 397 "Querier.HeadChunkLines", r.Querier.Store.Chunk.HeadChunkLines, 398 "Querier.DecompressedBytes", humanize.Bytes(uint64(r.Querier.Store.Chunk.DecompressedBytes)), 399 "Querier.DecompressedLines", r.Querier.Store.Chunk.DecompressedLines, 400 "Querier.CompressedBytes", humanize.Bytes(uint64(r.Querier.Store.Chunk.CompressedBytes)), 401 "Querier.TotalDuplicates", r.Querier.Store.Chunk.TotalDuplicates, 402 ) 403 r.Caches.Log(log) 404 r.Summary.Log(log) 405 } 406 407 func (s Summary) Log(log log.Logger) { 408 _ = log.Log( 409 "Summary.BytesProcessedPerSecond", humanize.Bytes(uint64(s.BytesProcessedPerSecond)), 410 "Summary.LinesProcessedPerSecond", s.LinesProcessedPerSecond, 411 "Summary.TotalBytesProcessed", humanize.Bytes(uint64(s.TotalBytesProcessed)), 412 "Summary.TotalLinesProcessed", s.TotalLinesProcessed, 413 "Summary.ExecTime", ConvertSecondsToNanoseconds(s.ExecTime), 414 "Summary.QueueTime", ConvertSecondsToNanoseconds(s.QueueTime), 415 ) 416 } 417 418 func (c Caches) Log(log log.Logger) { 419 _ = log.Log( 420 "Cache.Chunk.Requests", c.Chunk.Requests, 421 "Cache.Chunk.EntriesRequested", c.Chunk.EntriesRequested, 422 "Cache.Chunk.EntriesFound", c.Chunk.EntriesFound, 423 "Cache.Chunk.EntriesStored", c.Chunk.EntriesStored, 424 "Cache.Chunk.BytesSent", humanize.Bytes(uint64(c.Chunk.BytesSent)), 425 "Cache.Chunk.BytesReceived", humanize.Bytes(uint64(c.Chunk.BytesReceived)), 426 "Cache.Index.Requests", c.Index.Requests, 427 "Cache.Index.EntriesRequested", c.Index.EntriesRequested, 428 "Cache.Index.EntriesFound", c.Index.EntriesFound, 429 "Cache.Index.EntriesStored", c.Index.EntriesStored, 430 "Cache.Index.BytesSent", humanize.Bytes(uint64(c.Index.BytesSent)), 431 "Cache.Index.BytesReceived", humanize.Bytes(uint64(c.Index.BytesReceived)), 432 "Cache.Result.Requests", c.Result.Requests, 433 "Cache.Result.EntriesRequested", c.Result.EntriesRequested, 434 "Cache.Result.EntriesFound", c.Result.EntriesFound, 435 "Cache.Result.EntriesStored", c.Result.EntriesStored, 436 "Cache.Result.BytesSent", humanize.Bytes(uint64(c.Result.BytesSent)), 437 "Cache.Result.BytesReceived", humanize.Bytes(uint64(c.Result.BytesReceived)), 438 ) 439 }