github.com/google/cloudprober@v0.11.3/surfacers/file/file.go (about) 1 // Copyright 2017-2020 The Cloudprober 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 file implements the "file" surfacer. 16 package file 17 18 import ( 19 "context" 20 "fmt" 21 "os" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 27 "github.com/google/cloudprober/logger" 28 "github.com/google/cloudprober/metrics" 29 "github.com/google/cloudprober/surfacers/common/compress" 30 "github.com/google/cloudprober/surfacers/common/options" 31 32 configpb "github.com/google/cloudprober/surfacers/file/proto" 33 ) 34 35 // Surfacer structures for writing onto a GCE instance's serial port. Keeps 36 // track of an output file which the incoming data is serialized onto (one entry 37 // per line). 38 type Surfacer struct { 39 // Configuration 40 c *configpb.SurfacerConf 41 opts *options.Options 42 43 // Channel for incoming data. 44 inChan chan *metrics.EventMetrics 45 processInputWg sync.WaitGroup 46 47 // Output file for serializing to 48 outf *os.File 49 50 // Cloud logger 51 l *logger.Logger 52 53 // Each output message has a unique id. This field keeps the record of 54 // that. 55 id int64 56 57 compressionBuffer *compress.CompressionBuffer 58 } 59 60 func (s *Surfacer) processInput(ctx context.Context) { 61 defer s.processInputWg.Done() 62 63 if !s.c.GetCompressionEnabled() { 64 } 65 for { 66 select { 67 // Write the EventMetrics to file as string. 68 case em, ok := <-s.inChan: 69 if !ok { 70 return 71 } 72 var emStr strings.Builder 73 emStr.WriteString(s.c.GetPrefix()) 74 emStr.WriteByte(' ') 75 emStr.WriteString(strconv.FormatInt(s.id, 10)) 76 emStr.WriteByte(' ') 77 emStr.WriteString(em.String()) 78 s.id++ 79 80 // If compression is not enabled, write line to file and continue. 81 if !s.c.GetCompressionEnabled() { 82 if _, err := s.outf.WriteString(emStr.String() + "\n"); err != nil { 83 s.l.Errorf("Unable to write data to %s. Err: %v", s.c.GetFilePath(), err) 84 } 85 } else { 86 s.compressionBuffer.WriteLineToBuffer(emStr.String()) 87 } 88 89 case <-ctx.Done(): 90 return 91 } 92 } 93 } 94 95 func (s *Surfacer) init(ctx context.Context, id int64) error { 96 s.inChan = make(chan *metrics.EventMetrics, s.opts.MetricsBufferSize) 97 s.id = id 98 99 // File handle for the output file 100 if s.c.GetFilePath() == "" { 101 s.outf = os.Stdout 102 } else { 103 outf, err := os.Create(s.c.GetFilePath()) 104 if err != nil { 105 return fmt.Errorf("failed to create file for writing: %v", err) 106 } 107 s.outf = outf 108 } 109 110 if s.c.GetCompressionEnabled() { 111 s.compressionBuffer = compress.NewCompressionBuffer(ctx, func(data []byte) { 112 if _, err := s.outf.Write(append(data, '\n')); err != nil { 113 s.l.Errorf("Unable to write data to %s. Err: %v", s.outf.Name(), err) 114 } 115 }, s.opts.MetricsBufferSize/10, s.l) 116 } 117 118 // Start a goroutine to run forever, polling on the inChan. Allows 119 // for the surfacer to write asynchronously to the serial port. 120 s.processInputWg.Add(1) 121 go s.processInput(ctx) 122 123 return nil 124 } 125 126 // close closes the input channel, waits for input processing to finish, 127 // and closes the compression buffer if open. 128 func (s *Surfacer) close() { 129 close(s.inChan) 130 s.processInputWg.Wait() 131 132 if s.compressionBuffer != nil { 133 s.compressionBuffer.Close() 134 } 135 136 s.outf.Close() 137 } 138 139 // Write queues the incoming data into a channel. This channel is watched by a 140 // goroutine that actually writes data to a file ((usually set as a GCE 141 // instance's serial port). 142 func (s *Surfacer) Write(ctx context.Context, em *metrics.EventMetrics) { 143 select { 144 case s.inChan <- em: 145 default: 146 s.l.Errorf("Surfacer's write channel is full, dropping new data.") 147 } 148 } 149 150 // New initializes a Surfacer for serializing data into a file (usually set 151 // as a GCE instance's serial port). This Surfacer does not utilize the Google 152 // cloud logger because it is unlikely to fail reportably after the call to 153 // New. 154 func New(ctx context.Context, config *configpb.SurfacerConf, opts *options.Options, l *logger.Logger) (*Surfacer, error) { 155 s := &Surfacer{ 156 c: config, 157 opts: opts, 158 l: l, 159 } 160 161 // Get a unique id from the nano timestamp. This id is 162 // used to uniquely identify the data strings on the 163 // serial port. Only requirement for this id is that it 164 // should only go up for a particular instance. We don't 165 // call time.Now().UnixNano() for each string that we 166 // print as it's an expensive call and we don't really 167 // make use of its value. 168 id := time.Now().UnixNano() 169 170 return s, s.init(ctx, id) 171 }