go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butler/output/memory/memory.go (about)

     1  // Copyright 2021 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package memory implements an in-memory sink for the logdog Butler.
    16  //
    17  // This is meant for absorbing log data during testing of applications
    18  // which expect a live Butler.
    19  package memory
    20  
    21  import (
    22  	"strings"
    23  	"sync"
    24  
    25  	"google.golang.org/protobuf/proto"
    26  
    27  	"go.chromium.org/luci/common/errors"
    28  	"go.chromium.org/luci/logdog/api/logpb"
    29  	"go.chromium.org/luci/logdog/client/butler/bootstrap"
    30  	"go.chromium.org/luci/logdog/client/butler/output"
    31  )
    32  
    33  // Output implements the butler output.Output interface, but just
    34  // accumulates the data in memory.
    35  //
    36  // For simplicity, this only retains a subset of the data transmitted
    37  // by SendBundle (e.g. no timestamps, indexes, etc.).
    38  //
    39  // This assumes that SendBundle is called in order.
    40  type Output struct {
    41  	mu      sync.RWMutex
    42  	streams map[streamKey]*FakeStream
    43  	stats   output.StatsBase
    44  }
    45  
    46  // GetStream returns the *FakeStream corresponding to the given prefix and
    47  // stream name.
    48  //
    49  // If no such stream was opened, returns nil.
    50  func (o *Output) GetStream(prefix, stream string) *FakeStream {
    51  	o.mu.Lock()
    52  	defer o.mu.Unlock()
    53  	return o.streams[streamKey{prefix, stream}]
    54  }
    55  
    56  var _ output.Output = (*Output)(nil)
    57  
    58  type streamKey struct {
    59  	Prefix string
    60  	Name   string
    61  }
    62  
    63  // FakeStream holds the data recorded by a single stream in Output.
    64  type FakeStream struct {
    65  	stype logpb.StreamType
    66  	tags  map[string]string
    67  
    68  	mu          sync.Mutex
    69  	lastIsFinal bool
    70  	data        []*strings.Builder
    71  }
    72  
    73  // StreamType returns the categorization of this stream
    74  // (TEXT, BINARY, DATAGRAM).
    75  func (fs *FakeStream) StreamType() logpb.StreamType {
    76  	return fs.stype
    77  }
    78  
    79  // Tags returns any tags set for this stream.
    80  func (fs *FakeStream) Tags() map[string]string {
    81  	ret := make(map[string]string, len(fs.tags))
    82  	for k, v := range fs.tags {
    83  		ret[k] = v
    84  	}
    85  	return ret
    86  }
    87  
    88  // AllData returns all datagrams for this stream.
    89  //
    90  // If this is a BINARY or TEXT stream, this will always
    91  // return exactly one string.
    92  //
    93  // If fs == nil, returns nil.
    94  func (fs *FakeStream) AllData() []string {
    95  	if fs == nil {
    96  		return nil
    97  	}
    98  
    99  	fs.mu.Lock()
   100  	defer fs.mu.Unlock()
   101  	ret := make([]string, len(fs.data))
   102  	for i, dat := range fs.data {
   103  		ret[i] = dat.String()
   104  	}
   105  	return ret
   106  }
   107  
   108  // LastData returns the last datagram for this stream.
   109  //
   110  // If this is a BINARY or TEXT stream, this returns the
   111  // stream data.
   112  //
   113  // If fs == nil, returns "".
   114  func (fs *FakeStream) LastData() string {
   115  	if fs == nil {
   116  		return ""
   117  	}
   118  
   119  	fs.mu.Lock()
   120  	defer fs.mu.Unlock()
   121  
   122  	return fs.data[len(fs.data)-1].String()
   123  }
   124  
   125  func (fs *FakeStream) addData(be *logpb.ButlerLogBundle_Entry) {
   126  	fs.mu.Lock()
   127  	defer fs.mu.Unlock()
   128  
   129  	switch fs.stype {
   130  	case logpb.StreamType_TEXT:
   131  		for _, logEntry := range be.Logs {
   132  			for _, line := range logEntry.GetText().Lines {
   133  				fs.data[0].Write(line.Value)
   134  				fs.data[0].WriteString(line.Delimiter)
   135  			}
   136  		}
   137  	case logpb.StreamType_BINARY:
   138  		for _, logEntry := range be.Logs {
   139  			fs.data[0].Write(logEntry.GetBinary().Data)
   140  		}
   141  	case logpb.StreamType_DATAGRAM:
   142  		for _, logEntry := range be.Logs {
   143  			dg := logEntry.GetDatagram()
   144  			if fs.lastIsFinal {
   145  				fs.data = append(fs.data, &strings.Builder{})
   146  				fs.lastIsFinal = false
   147  			}
   148  			fs.data[len(fs.data)-1].Write(dg.Data)
   149  			if dg.Partial == nil {
   150  				fs.lastIsFinal = true
   151  			}
   152  		}
   153  	default:
   154  		panic(errors.Reason("unknown StreamType: %s", fs.stype).Err())
   155  	}
   156  }
   157  
   158  // SendBundle implements output.Output
   159  func (o *Output) SendBundle(b *logpb.ButlerLogBundle) error {
   160  	o.mu.Lock()
   161  	defer o.mu.Unlock()
   162  	o.stats.F.SentMessages += int64(len(b.Entries))
   163  	o.stats.F.SentBytes += int64(proto.Size(b))
   164  
   165  	if o.streams == nil {
   166  		o.streams = map[streamKey]*FakeStream{}
   167  	}
   168  
   169  	for _, bundleEntry := range b.Entries {
   170  		sk := streamKey{bundleEntry.Desc.Prefix, bundleEntry.Desc.Name}
   171  		cur, ok := o.streams[sk]
   172  		if !ok {
   173  			cur = &FakeStream{
   174  				stype: bundleEntry.Desc.StreamType,
   175  				data:  []*strings.Builder{{}},
   176  			}
   177  			cur.tags = make(map[string]string, len(bundleEntry.Desc.Tags))
   178  			for k, v := range bundleEntry.Desc.Tags {
   179  				cur.tags[k] = v
   180  			}
   181  			o.streams[sk] = cur
   182  		}
   183  
   184  		cur.addData(bundleEntry)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // MaxSendBundles implements output.Output
   191  func (o *Output) MaxSendBundles() int {
   192  	return 1
   193  }
   194  
   195  // Stats implements output.Output
   196  func (o *Output) Stats() output.Stats {
   197  	o.mu.RLock()
   198  	defer o.mu.RUnlock()
   199  	statsCp := o.stats
   200  	return &statsCp
   201  }
   202  
   203  // URLConstructionEnv implements output.Output
   204  func (o *Output) URLConstructionEnv() bootstrap.Environment {
   205  	return bootstrap.Environment{
   206  		Project: "memory",
   207  		Prefix:  "memory",
   208  	}
   209  }
   210  
   211  // MaxSize returns a large number instead of 0 because butler has bugs.
   212  func (o *Output) MaxSize() int { return 1024 * 1024 * 1024 }
   213  
   214  // Close implements output.Output
   215  func (o *Output) Close() {}