github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/agent/agent.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"hash/crc32"
     8  	"io"
     9  	"io/fs"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime/debug"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/nikandfor/errors"
    18  
    19  	"github.com/nikandfor/tlog"
    20  	"github.com/nikandfor/tlog/tlio"
    21  	"github.com/nikandfor/tlog/tlwire"
    22  	"github.com/nikandfor/tlog/tlz"
    23  )
    24  
    25  type (
    26  	Agent struct { //nolint:maligned
    27  		path string
    28  
    29  		sync.Mutex
    30  
    31  		files []*file
    32  		subs  map[int64]*sub
    33  		subid int64
    34  
    35  		// end of Mutex
    36  
    37  		Partition   time.Duration
    38  		MaxFileSize int64
    39  
    40  		KeyTimestamp string
    41  
    42  		Stderr io.Writer
    43  
    44  		d tlwire.Decoder
    45  	}
    46  
    47  	file struct {
    48  		sum    uint32
    49  		labels []byte
    50  		start  time.Time
    51  
    52  		sync.Mutex
    53  
    54  		io.Writer
    55  
    56  		// end of Mutex
    57  
    58  		a *Agent
    59  	}
    60  
    61  	sub struct {
    62  		id int64
    63  		io.Writer
    64  	}
    65  )
    66  
    67  func New(db string) (*Agent, error) {
    68  	a := &Agent{
    69  		path: db,
    70  
    71  		subs: make(map[int64]*sub),
    72  
    73  		Partition:    3 * time.Hour,
    74  		KeyTimestamp: tlog.KeyTimestamp,
    75  
    76  		Stderr: os.Stderr,
    77  	}
    78  
    79  	err := a.openFiles()
    80  	if err != nil {
    81  		return a, errors.Wrap(err, "open files")
    82  	}
    83  
    84  	return a, nil
    85  }
    86  
    87  func (a *Agent) openFiles() (err error) {
    88  	err = filepath.WalkDir(a.path, func(path string, d fs.DirEntry, err error) error {
    89  		if path == a.path {
    90  			return nil
    91  		}
    92  		if d.IsDir() {
    93  			return fs.SkipDir
    94  		}
    95  
    96  		base := filepath.Base(path)
    97  		ext := filepath.Ext(path)
    98  
    99  		if ext != ".tlz" || !strings.HasPrefix(base, "events_") {
   100  			return nil
   101  		}
   102  
   103  		ff, err := os.Open(path)
   104  		if err != nil {
   105  			return errors.Wrap(err, "open file")
   106  		}
   107  
   108  		defer func() {
   109  			e := ff.Close()
   110  			if err == nil {
   111  				err = errors.Wrap(e, "close file")
   112  			}
   113  		}()
   114  
   115  		dec := tlz.NewDecoder(ff)
   116  		sd := tlwire.NewStreamDecoder(dec)
   117  
   118  		msg, err := sd.Decode()
   119  		if err != nil {
   120  			return errors.Wrap(err, "decode message")
   121  		}
   122  
   123  		_, err = a.getFile(msg)
   124  		if err != nil {
   125  			return errors.Wrap(err, "get file")
   126  		}
   127  
   128  		return nil
   129  	})
   130  
   131  	return nil
   132  }
   133  
   134  func (a *Agent) Write(p []byte) (n int, err error) {
   135  	defer func() {
   136  		perr := recover()
   137  
   138  		if err == nil && perr == nil {
   139  			return
   140  		}
   141  
   142  		if perr != nil {
   143  			fmt.Fprintf(a.Stderr, "panic: %v (pos %x)\n", perr, n)
   144  		} else {
   145  			fmt.Fprintf(a.Stderr, "parse error: %+v (pos %x)\n", err, n)
   146  		}
   147  		fmt.Fprintf(a.Stderr, "dump\n%v", tlwire.Dump(p))
   148  		fmt.Fprintf(a.Stderr, "hex dump\n%v", hex.Dump(p))
   149  
   150  		s := debug.Stack()
   151  		fmt.Fprintf(a.Stderr, "%s", s)
   152  	}()
   153  
   154  	f, err := a.getFile(p)
   155  	if err != nil {
   156  		return 0, err
   157  	}
   158  
   159  	n, err = f.Write(p)
   160  
   161  	func() {
   162  		defer a.Unlock()
   163  		a.Lock()
   164  
   165  		for _, sub := range a.subs {
   166  			_, _ = sub.Writer.Write(p)
   167  		}
   168  	}()
   169  
   170  	return
   171  }
   172  
   173  func (a *Agent) Close() (err error) {
   174  	a.Lock()
   175  	defer a.Unlock()
   176  
   177  	for _, f := range a.files {
   178  		e := f.Close()
   179  		if err == nil {
   180  			err = errors.Wrap(e, "file %v", f.sum)
   181  		}
   182  	}
   183  
   184  	return err
   185  }
   186  
   187  func (a *Agent) getFile(p []byte) (f *file, err error) {
   188  	if tlog.If("dump") {
   189  		defer func() {
   190  			var sum uint32
   191  			if f != nil {
   192  				sum = f.sum
   193  			}
   194  
   195  			tlog.Printw("message", "sum", tlog.NextAsHex, sum, "msg", tlog.RawMessage(p))
   196  		}()
   197  	}
   198  
   199  	defer a.Unlock()
   200  	a.Lock()
   201  
   202  	ts, labels, err := a.parseEventHeader(p)
   203  	if err != nil {
   204  		return nil, errors.Wrap(err, "parse event header")
   205  	}
   206  
   207  	start := time.Unix(0, ts).UTC().Truncate(a.Partition)
   208  
   209  	f = a.getPart(labels, start)
   210  
   211  	return f, nil
   212  }
   213  
   214  func (a *Agent) getPart(labels []byte, start time.Time) *file {
   215  	sum := crc32.ChecksumIEEE(labels)
   216  
   217  	for _, f := range a.files {
   218  		if f.sum == sum && f.start.UnixNano() == start.UnixNano() && bytes.Equal(f.labels, labels) {
   219  			return f
   220  		}
   221  	}
   222  
   223  	f := &file{
   224  		sum:    sum,
   225  		labels: append([]byte{}, labels...),
   226  		start:  start,
   227  
   228  		a: a,
   229  	}
   230  
   231  	a.files = append(a.files, f)
   232  
   233  	tlog.Printw("open file", "sum", tlog.NextAsHex, sum, "labels", tlog.RawTag(tlwire.Map, -1), tlog.RawMessage(labels), tlog.Break)
   234  
   235  	return f
   236  }
   237  
   238  func (a *Agent) parseEventHeader(p []byte) (ts int64, labels []byte, err error) {
   239  	tag, els, i := a.d.Tag(p, 0)
   240  	if tag != tlwire.Map {
   241  		err = errors.New("expected map")
   242  		return
   243  	}
   244  
   245  	var k []byte
   246  	var sub int64
   247  	var end int
   248  
   249  	for el := 0; els == -1 || el < int(els); el++ {
   250  		if els == -1 && a.d.Break(p, &i) {
   251  			break
   252  		}
   253  
   254  		st := i
   255  
   256  		k, i = a.d.Bytes(p, i)
   257  		if len(k) == 0 {
   258  			err = errors.New("empty key")
   259  			return
   260  		}
   261  
   262  		tag, sub, end = a.d.SkipTag(p, i)
   263  		if tag != tlwire.Semantic {
   264  			i = a.d.Skip(p, i)
   265  			continue
   266  		}
   267  
   268  		switch {
   269  		case sub == tlwire.Time && string(k) == a.KeyTimestamp:
   270  			ts, i = a.d.Timestamp(p, i)
   271  		case sub == tlog.WireLabel:
   272  			// labels = crc32.Update(labels, crc32.IEEETable, p[st:end])
   273  			labels = append(labels, p[st:end]...)
   274  		}
   275  
   276  		i = end
   277  	}
   278  
   279  	return
   280  }
   281  
   282  func (f *file) Write(p []byte) (n int, err error) {
   283  	defer f.Unlock()
   284  	f.Lock()
   285  
   286  	if f.Writer == nil {
   287  		f.Writer, err = f.a.openWriter(f)
   288  		if err != nil {
   289  			return 0, errors.Wrap(err, "open writer")
   290  		}
   291  	}
   292  
   293  	n, err = f.Writer.Write(p)
   294  
   295  	return
   296  }
   297  
   298  func (f *file) Close() (err error) {
   299  	if c, ok := f.Writer.(io.Closer); ok {
   300  		e := c.Close()
   301  		if err == nil {
   302  			err = errors.Wrap(e, "close writer")
   303  		}
   304  	}
   305  
   306  	return err
   307  }
   308  
   309  func (a *Agent) openWriter(f *file) (io.Writer, error) {
   310  	name := filepath.Join(a.path, fmt.Sprintf("events_%08x_%s.tlz", f.sum, f.start.Format("2006-01-02T15-04")))
   311  	dir := filepath.Dir(name)
   312  
   313  	err := os.MkdirAll(dir, 0o755)
   314  	if err != nil {
   315  		return nil, errors.Wrap(err, "mkdir")
   316  	}
   317  
   318  	ff, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
   319  	if err != nil {
   320  		return nil, errors.Wrap(err, "open file")
   321  	}
   322  
   323  	w := tlz.NewEncoder(ff, tlz.MiB)
   324  
   325  	return tlio.WriteCloser{
   326  		Writer: w,
   327  		Closer: ff,
   328  	}, nil
   329  }