github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/common/telemetry/telemetry.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package telemetry 20 21 import ( 22 "context" 23 "fmt" 24 "sort" 25 "time" 26 27 "github.com/e154/smart-home/common" 28 ) 29 30 type StatusCode string 31 32 const ( 33 Ok = StatusCode("Ok") 34 Error = StatusCode("Error") 35 ) 36 37 type Span struct { 38 traceName string 39 name string 40 Ctx context.Context 41 started time.Time 42 ended *time.Time 43 Level int 44 Num int 45 status StatusCode 46 description string 47 attributes map[string]string 48 } 49 50 func Start(ctx context.Context, name string) (context.Context, *Span) { 51 var level = 1 52 var num = 1 53 if p, ok := SpanFromContext(ctx); ok { 54 num += p.Num 55 level = p.Level 56 if p.ended == nil { 57 level += 1 58 } 59 } 60 span := &Span{ 61 name: name, 62 Level: level, 63 started: time.Now(), 64 Ctx: ctx, 65 Num: num, 66 } 67 68 return context.WithValue(ctx, "span", span), span 69 } 70 71 func (s *Span) End() { 72 s.ended = common.Time(time.Now()) 73 if s.status == "" { 74 s.status = Ok 75 } 76 } 77 78 func (s *Span) SetStatus(code StatusCode, description string) { 79 s.status = code 80 s.description = description 81 } 82 83 func (s *Span) TimeEstimate() time.Duration { 84 if s.ended != nil { 85 return s.ended.Sub(s.started) 86 } 87 return 0 88 } 89 90 func (s *Span) SetAttributes(key string, value interface{}) { 91 if s.attributes == nil { 92 s.attributes = make(map[string]string) 93 } 94 s.attributes[key] = fmt.Sprintf("%v", value) 95 } 96 97 func SpanFromContext(ctx context.Context) (span *Span, ok bool) { 98 if ctx == nil { 99 return 100 } 101 span, ok = ctx.Value("span").(*Span) 102 return 103 } 104 105 type StateItem struct { 106 Name string 107 Num int 108 Start time.Time 109 End *time.Time 110 TimeEstimate time.Duration 111 Attributes map[string]string 112 Status StatusCode 113 Level int 114 } 115 116 type Telemetry []*StateItem 117 118 func (s Telemetry) Len() int { return len(s) } 119 func (s Telemetry) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 120 func (s Telemetry) Less(i, j int) bool { return s[i].Num < s[j].Num } 121 122 func Unpack(ctx context.Context) Telemetry { 123 _levels := make(Telemetry, 0) 124 LOOP: 125 span, ok := SpanFromContext(ctx) 126 if !ok { 127 return _levels 128 } 129 130 _levels = append(_levels, &StateItem{ 131 Name: span.name, 132 Num: span.Num, 133 Start: span.started, 134 End: span.ended, 135 TimeEstimate: span.TimeEstimate(), 136 Attributes: span.attributes, 137 Status: span.status, 138 Level: span.Level, 139 }) 140 141 sort.Sort(_levels) 142 143 if span.Ctx != nil { 144 ctx = span.Ctx 145 goto LOOP 146 } 147 148 return nil 149 }