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  }