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  }