tlog.app/go/tlog@v0.23.1/ext/tlclick/click.go (about)

     1  //go:build ignore
     2  
     3  package tlclick
     4  
     5  import (
     6  	"context"
     7  	"encoding/hex"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
    12  	"github.com/google/uuid"
    13  	"tlog.app/go/errors"
    14  	"tlog.app/go/loc"
    15  	"tlog.app/go/tlog"
    16  	"tlog.app/go/tlog/convert"
    17  	"tlog.app/go/tlog/tlwire"
    18  )
    19  
    20  type (
    21  	UUID = uuid.UUID
    22  
    23  	Click struct {
    24  		c driver.Conn
    25  
    26  		KeyTimestamp string
    27  		KeySpan      string
    28  
    29  		JSON *convert.JSON
    30  
    31  		b         driver.Batch
    32  		lastFlush time.Time
    33  
    34  		spans   []UUID
    35  		related []UUID
    36  
    37  		dataJSON   []byte
    38  		labelsJSON []byte
    39  
    40  		labelsBuf []byte
    41  		labelsArr [][]byte
    42  	}
    43  )
    44  
    45  func New(c driver.Conn) *Click {
    46  	d := &Click{
    47  		c: c,
    48  
    49  		KeyTimestamp: tlog.KeyTimestamp,
    50  		KeySpan:      tlog.KeySpan,
    51  	}
    52  
    53  	d.JSON = convert.NewJSON(nil)
    54  	d.JSON.AppendNewLine = false
    55  
    56  	return d
    57  }
    58  
    59  func (d *Click) Query(ctx context.Context, w io.Writer, ts int64, q string) error {
    60  	return nil
    61  }
    62  
    63  func (d *Click) CreateTables(ctx context.Context) error {
    64  	err := d.c.Exec(ctx, `CREATE TABLE IF NOT EXISTS events (
    65  	tlog String,
    66  	json String,
    67  
    68  	labels       String COMMENT 'json formatted',
    69  	_labels      Array(String) COMMENT 'tlog pairs',
    70  	_labels_hash UInt64 MATERIALIZED cityHash64(arrayStringConcat(_labels)),
    71  
    72  	ts DateTime64(9, 'UTC'),
    73  
    74  	spans   Array(UUID),
    75  	related Array(UUID),
    76  
    77  	timestamp String ALIAS visitParamExtractString(json, '_t'),
    78  	span      String ALIAS toUUIDOrZero(visitParamExtractString(json, '_s')),
    79  	parent    String ALIAS toUUIDOrZero(visitParamExtractString(json, '_p')),
    80  	caller    String ALIAS visitParamExtractString(json, '_c'),
    81  	message   String ALIAS visitParamExtractString(json, '_m'),
    82  	msg       String ALIAS message,
    83  	event     String ALIAS visitParamExtractString(json, '_k'),
    84  	elapsed   Int64  ALIAS visitParamExtractInt(json, '_e'),
    85  	log_level Int8   ALIAS visitParamExtractInt(json, '_l'),
    86  	error     String ALIAS visitParamExtractString(json, 'err'),
    87  
    88  	kvs Array(Tuple(String, String)) ALIAS arrayMap(k -> (k, JSONExtractRaw(json, k)), arrayFilter(k -> k NOT IN ('_s', '_t', '_c', '_m'), JSONExtractKeys(json))),
    89  
    90  	minute  DateTime ALIAS        toStartOfMinute(ts),
    91  	hour    DateTime ALIAS        toStartOfHour(ts),
    92  	day     Date     MATERIALIZED toStartOfDay(ts),
    93  	week    Date     ALIAS        toStartOfWeek(ts),
    94  	month   Date     ALIAS        toStartOfMonth(ts),
    95  	year    Date     ALIAS        toStartOfYear(ts),
    96  )
    97  ENGINE ReplacingMergeTree
    98  ORDER BY ts
    99  PARTITION BY (day, _labels_hash)
   100  `)
   101  	if err != nil {
   102  		return errors.Wrap(err, "events")
   103  	}
   104  
   105  	err = d.c.Exec(ctx, `CREATE OR REPLACE VIEW spans AS
   106  SELECT
   107  	span,
   108  	min(ts) AS start,
   109  	max(ts) AS end,
   110  	anyIf(message, event = 's') AS name,
   111  	round(anyIf(elapsed, event = 'f') / 1e9, 1) AS elapsed_s,
   112  	anyIf(error, event = 'f') AS error
   113  FROM events GROUP BY span
   114  `)
   115  	if err != nil {
   116  		return errors.Wrap(err, "spans")
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func (d *Click) Write(p []byte) (n int, err error) {
   123  	for n < len(p) {
   124  		n, err = d.writeEvent(p, n)
   125  		if err != nil {
   126  			return n, err
   127  		}
   128  	}
   129  
   130  	return
   131  }
   132  
   133  func (d *Click) writeEvent(p []byte, st int) (next int, err error) {
   134  	if tlog.If("dump") {
   135  		defer func() {
   136  			tlog.Printw("event", "st", tlog.NextAsHex, st, "next", tlog.NextAsHex, next, "err", err, "msg", tlog.RawMessage(p))
   137  		}()
   138  	}
   139  
   140  	ts, next, err := d.parseEvent(p, st)
   141  	if err != nil {
   142  		return st, errors.Wrap(err, "parse message")
   143  	}
   144  
   145  	if d.b == nil {
   146  		ctx := context.Background()
   147  
   148  		d.b, err = d.c.PrepareBatch(ctx, `INSERT INTO events (
   149  			tlog, json,
   150  			_labels, labels,
   151  			ts,
   152  			spans, related
   153  		) VALUES`)
   154  		if err != nil {
   155  			return st, errors.Wrap(err, "prepare batch")
   156  		}
   157  
   158  		d.lastFlush = time.Now()
   159  	}
   160  
   161  	err = d.b.Append(
   162  		p[st:next], d.dataJSON,
   163  		d.labelsArr, d.labelsJSON,
   164  		time.Unix(0, ts).UTC(),
   165  		d.spans, d.related,
   166  	)
   167  	if err != nil {
   168  		return st, errors.Wrap(err, "append row")
   169  	}
   170  
   171  	if time.Since(d.lastFlush) > 1000*time.Millisecond {
   172  		err = d.Flush()
   173  		if err != nil {
   174  			return st, errors.Wrap(err, "flush")
   175  		}
   176  	}
   177  
   178  	return next, nil
   179  }
   180  
   181  func (d *Click) Flush() (err error) {
   182  	if d.b != nil {
   183  		e := d.b.Send()
   184  		tlog.Printw("flush", "err", e)
   185  		if err == nil {
   186  			err = errors.Wrap(e, "flush batch")
   187  		}
   188  
   189  		d.b = nil
   190  	}
   191  
   192  	return err
   193  }
   194  
   195  func (d *Click) Close() error {
   196  	return d.Flush()
   197  }
   198  
   199  func (d *Click) parseEvent(p []byte, st int) (ts int64, i int, err error) {
   200  	defer func() {
   201  		pe := recover()
   202  		if pe == nil {
   203  			return
   204  		}
   205  
   206  		tlog.V("panic_dump_hex").Printw("panic", "panic", pe, "st", tlog.NextAsHex, st, "pos", tlog.NextAsHex, i, "buf", hex.Dump(p))
   207  		tlog.V("panic_dump_wire").Printw("panic", "panic", pe, "st", tlog.NextAsHex, st, "pos", tlog.NextAsHex, i, "dump", tlwire.Dump(p))
   208  		tlog.Printw("panic", "panic", pe, "from", loc.Callers(1, 10))
   209  
   210  		panic(pe)
   211  	}()
   212  
   213  	var dec tlwire.Decoder
   214  
   215  	d.spans = d.spans[:0]
   216  	d.related = d.related[:0]
   217  
   218  	d.dataJSON = d.dataJSON[:0]
   219  	d.labelsJSON = d.labelsJSON[:0]
   220  
   221  	d.labelsBuf = d.labelsBuf[:0]
   222  	d.labelsArr = d.labelsArr[:0]
   223  
   224  	defer func() {
   225  		if len(d.dataJSON) != 0 {
   226  			d.dataJSON = append(d.dataJSON, '}')
   227  		}
   228  
   229  		if len(d.labelsJSON) != 0 {
   230  			d.labelsJSON = append(d.labelsJSON, '}')
   231  		}
   232  	}()
   233  
   234  	tag, els, i := dec.Tag(p, st)
   235  	if tag != tlwire.Map {
   236  		err = errors.New("expected map")
   237  		return
   238  	}
   239  
   240  	var k []byte
   241  	var sub int64
   242  	var end int
   243  
   244  	for el := 0; els == -1 || el < int(els); el++ {
   245  		if els == -1 && dec.Break(p, &i) {
   246  			break
   247  		}
   248  
   249  		st := i
   250  
   251  		k, i = dec.Bytes(p, i)
   252  		if len(k) == 0 {
   253  			err = errors.New("empty key")
   254  			return
   255  		}
   256  
   257  		tag, sub, end = dec.SkipTag(p, i)
   258  
   259  		{
   260  			var jb []byte
   261  
   262  			if tag == tlwire.Semantic && sub == tlog.WireLabel {
   263  				jb = d.labelsJSON
   264  			} else {
   265  				jb = d.dataJSON
   266  			}
   267  
   268  			if len(jb) == 0 {
   269  				jb = append(jb, '{')
   270  			} else {
   271  				jb = append(jb, ',')
   272  			}
   273  
   274  			jb, _ = d.JSON.ConvertValue(jb, p, st)
   275  
   276  			jb = append(jb, ':')
   277  
   278  			jb, _ = d.JSON.ConvertValue(jb, p, i)
   279  
   280  			if tag == tlwire.Semantic && sub == tlog.WireLabel {
   281  				d.labelsJSON = jb
   282  			} else {
   283  				d.dataJSON = jb
   284  			}
   285  		}
   286  
   287  		if tag != tlwire.Semantic {
   288  			i = end
   289  			continue
   290  		}
   291  
   292  		switch {
   293  		case sub == tlwire.Time && string(k) == d.KeyTimestamp:
   294  			ts, i = dec.Timestamp(p, i)
   295  		case sub == tlog.WireLabel:
   296  			// labels = crc32.Update(labels, crc32.IEEETable, p[st:end])
   297  
   298  			lst := len(d.labelsBuf)
   299  			d.labelsBuf = append(d.labelsBuf, p[st:end]...)
   300  			d.labelsArr = append(d.labelsArr, d.labelsBuf[lst:])
   301  		case sub == tlog.WireID:
   302  			var id tlog.ID
   303  			_ = id.TlogParse(p, i)
   304  
   305  			u := UUID(id)
   306  
   307  			//tlog.Printw("parsed id", "id", id, "key", string(k), "key_span", d.KeySpan)
   308  
   309  			if string(k) == d.KeySpan {
   310  				d.spans = append(d.spans, u)
   311  			} else {
   312  				d.related = append(d.related, u)
   313  			}
   314  		}
   315  
   316  		i = end
   317  	}
   318  
   319  	return
   320  }