tlog.app/go/tlog@v0.23.1/agent/agent.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 stderrors "errors" 6 "fmt" 7 "hash/crc32" 8 "io" 9 "os" 10 "path/filepath" 11 "sync" 12 "time" 13 14 hlow "github.com/nikandfor/hacked/low" 15 "tlog.app/go/eazy" 16 "tlog.app/go/errors" 17 "tlog.app/go/loc" 18 19 "tlog.app/go/tlog" 20 "tlog.app/go/tlog/tlwire" 21 ) 22 23 type ( 24 Agent struct { 25 path string 26 27 mu sync.Mutex 28 29 subid int64 // last used 30 subs []sub 31 32 streams []*stream 33 files []*file 34 35 // end of mu 36 37 KeyTimestamp string 38 39 Partition time.Duration 40 FileSize int64 41 BlockSize int64 42 43 Stderr io.Writer 44 45 d tlwire.Decoder 46 } 47 48 stream struct { 49 labels []byte 50 sum uint32 51 52 file *file 53 54 z *eazy.Writer 55 zbuf hlow.Buf 56 boff int64 57 } 58 59 file struct { 60 w io.Writer 61 62 name string 63 64 part int64 65 ts int64 66 67 prev *file 68 69 mu sync.Mutex 70 71 off int64 72 index []ientry 73 } 74 75 ientry struct { 76 off int64 77 ts int64 78 } 79 ) 80 81 var ( 82 ErrUnknownSubscription = stderrors.New("unknown subscription") 83 84 ErrFileFull = stderrors.New("file is full") 85 ) 86 87 func New(path string) (*Agent, error) { 88 a := &Agent{ 89 path: path, 90 91 KeyTimestamp: tlog.KeyTimestamp, 92 Partition: 3 * time.Hour, 93 FileSize: eazy.GiB, 94 BlockSize: 16 * eazy.MiB, 95 96 Stderr: os.Stderr, 97 } 98 99 return a, nil 100 } 101 102 func (a *Agent) Write(p []byte) (n int, err error) { 103 defer a.mu.Unlock() 104 a.mu.Lock() 105 106 for n < len(p) { 107 ts, labels, err := a.parseEventHeader(p[n:]) 108 if err != nil { 109 return n, errors.Wrap(err, "parse event") 110 } 111 112 f, s, err := a.file(ts, labels, len(p[n:])) 113 if err != nil { 114 return n, errors.Wrap(err, "get file") 115 } 116 117 m, err := a.writeFile(s, f, p[n:], ts) 118 n += m 119 if errors.Is(err, ErrFileFull) { 120 continue 121 } 122 if err != nil { 123 return n, errors.Wrap(err, "write") 124 } 125 } 126 127 return 128 } 129 130 func (a *Agent) parseEventHeader(p []byte) (ts int64, labels []byte, err error) { 131 tag, els, i := a.d.Tag(p, 0) 132 if tag != tlwire.Map { 133 err = errors.New("expected map") 134 return 135 } 136 137 var k []byte 138 var sub int64 139 var end int 140 141 for el := 0; els == -1 || el < int(els); el++ { 142 if els == -1 && a.d.Break(p, &i) { 143 break 144 } 145 146 st := i 147 148 k, i = a.d.Bytes(p, i) 149 if len(k) == 0 { 150 err = errors.New("empty key") 151 return 152 } 153 154 tag, sub, end = a.d.SkipTag(p, i) 155 if tag != tlwire.Semantic { 156 i = a.d.Skip(p, i) 157 continue 158 } 159 160 switch { 161 case sub == tlwire.Time && string(k) == a.KeyTimestamp: 162 ts, i = a.d.Timestamp(p, i) 163 case sub == tlog.WireLabel: 164 // labels = crc32.Update(labels, crc32.IEEETable, p[st:end]) 165 labels = append(labels, p[st:end]...) 166 } 167 168 i = end 169 } 170 171 return 172 } 173 174 func (a *Agent) file(ts int64, labels []byte, size int) (*file, *stream, error) { 175 sum := crc32.ChecksumIEEE(labels) 176 177 var s *stream 178 179 for _, ss := range a.streams { 180 if ss.sum == sum && bytes.Equal(ss.labels, labels) { 181 s = ss 182 break 183 } 184 } 185 186 if s == nil { 187 s = &stream{ 188 labels: labels, 189 sum: sum, 190 } 191 192 s.z = eazy.NewWriter(&s.zbuf, eazy.MiB, 1024) 193 s.z.AppendMagic = true 194 195 a.streams = append(a.streams, s) 196 } 197 198 return s.file, s, nil 199 } 200 201 func (a *Agent) writeFile(s *stream, f *file, p []byte, ts int64) (n int, err error) { 202 tlog.Printw("write message", "i", geti(p)) 203 204 s.zbuf = s.zbuf[:0] 205 n, err = s.z.Write(p) 206 if err != nil { 207 return 0, errors.Wrap(err, "eazy") 208 } 209 210 part := time.Unix(0, ts).Truncate(a.Partition).UnixNano() 211 212 if f == nil || f.part != part || f.off+int64(len(s.zbuf)) > a.FileSize && f.off != 0 { 213 f, err = a.newFile(s, part, ts) 214 if err != nil { 215 return 0, errors.Wrap(err, "new file") 216 } 217 218 s.file = f 219 220 s.z.Reset(&s.zbuf) 221 s.boff = 0 222 } 223 224 defer f.mu.Unlock() 225 f.mu.Lock() 226 227 // tlog.Printw("write file", "file", f.name, "off", tlog.NextAsHex, f.off, "boff", tlog.NextAsHex, s.boff, "block", tlog.NextAsHex, a.BlockSize) 228 nextBlock := false 229 230 if nextBlock := s.boff+int64(len(s.zbuf)) > a.BlockSize && s.boff != 0; nextBlock { 231 err = a.padFile(s, f) 232 tlog.Printw("pad file", "off", tlog.NextAsHex, f.off, "err", err) 233 if err != nil { 234 return 0, errors.Wrap(err, "pad file") 235 } 236 237 s.z.Reset(&s.zbuf) 238 s.boff = 0 239 } 240 241 if len(f.index) == 0 || nextBlock { 242 tlog.Printw("append index", "off", tlog.NextAsHex, f.off, "ts", ts/1e9) 243 f.index = append(f.index, ientry{ 244 off: f.off, 245 ts: ts, 246 }) 247 } 248 249 if s.boff == 0 { 250 s.zbuf = s.zbuf[:0] 251 n, err = s.z.Write(p) 252 if err != nil { 253 return 0, errors.Wrap(err, "eazy") 254 } 255 } 256 257 n, err = f.w.Write(s.zbuf) 258 // tlog.Printw("write message", "zst", tlog.NextAsHex, zst, "n", tlog.NextAsHex, n, "err", err) 259 if err != nil { 260 return n, err 261 } 262 263 f.off += int64(n) 264 s.boff += int64(n) 265 266 return len(p), nil 267 } 268 269 func (a *Agent) newFile(s *stream, part, ts int64) (*file, error) { 270 // base := fmt.Sprintf("%08x/%08x_%08x.tlz", part/1e9, s.sum, ts/1e9) 271 base := fmt.Sprintf("%v/%08x_%08x.tlz", 272 time.Unix(0, part).UTC().Format("2006-01-02T15:04"), 273 s.sum, 274 ts/1e9, 275 ) 276 fname := filepath.Join(a.path, base) 277 dir := filepath.Dir(fname) 278 279 tlog.Printw("new file", "file", base, "from", loc.Callers(1, 2)) 280 281 err := os.MkdirAll(dir, 0o755) 282 if err != nil { 283 return nil, errors.Wrap(err, "mkdir") 284 } 285 286 w, err := os.OpenFile(fname, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) 287 if err != nil { 288 return nil, errors.Wrap(err, "open file") 289 } 290 291 f := &file{ 292 w: w, 293 name: fname, 294 295 prev: s.file, 296 297 part: part, 298 ts: ts, 299 300 // index: []ientry{{ 301 // off: 0, 302 // ts: ts, 303 // }}, 304 } 305 306 return f, nil 307 } 308 309 func (a *Agent) padFile(s *stream, f *file) error { 310 if f.off%int64(a.BlockSize) == 0 { 311 s.boff = 0 312 313 return nil 314 } 315 316 off := f.off + int64(a.BlockSize) - f.off%int64(a.BlockSize) 317 318 if s, ok := f.w.(interface { 319 Truncate(int64) error 320 io.Seeker 321 }); ok { 322 err := s.Truncate(off) 323 if err != nil { 324 return errors.Wrap(err, "truncate") 325 } 326 327 off, err = s.Seek(off, io.SeekStart) 328 if err != nil { 329 return errors.Wrap(err, "seek") 330 } 331 332 f.off = off 333 } else { 334 n, err := f.w.Write(make([]byte, off-f.off)) 335 if err != nil { 336 return errors.Wrap(err, "write padding") 337 } 338 339 f.off += int64(n) 340 } 341 342 return nil 343 } 344 345 func geti(p []byte) (x int64) { 346 var d tlwire.LowDecoder 347 348 tag, els, i := d.Tag(p, 0) 349 if tag != tlwire.Map { 350 return -1 351 } 352 353 var k []byte 354 var sub int64 355 var end int 356 357 for el := 0; els == -1 || el < int(els); el++ { 358 if els == -1 && d.Break(p, &i) { 359 break 360 } 361 362 k, i = d.Bytes(p, i) 363 if len(k) == 0 { 364 return -1 365 } 366 367 tag, sub, end = d.SkipTag(p, i) 368 if tag == tlwire.Int && string(k) == "i" { 369 return sub 370 } 371 372 i = end 373 } 374 375 return -1 376 }