コード例 #1
0
func (s *MySuite) TestAverage(c *C) {
	ctx, cancel := context.WithCancel(context.Background())
	e := NewExporter(ctx, s.Client, 10*time.Second)
	a := NewAverage(e, variable.NewFromString("/test/average"))

	a.Update(95, 1)
	a.Update(100, 1)
	a.Update(105, 1)

	// Force export
	streams := e.GetStreams()
	c.Assert(len(streams), Equals, 2)
	for _, stream := range streams {
		switch variable.ProtoToString(stream.Variable) {
		case "/test/average-total-count":
			c.Check(stream.Value[0].DoubleValue, Equals, 300.0)
		case "/test/average-overall-sum":
			c.Check(stream.Value[0].DoubleValue, Equals, 3.0)
		default:
			fmt.Printf("Invalid variable %s", variable.ProtoToString(stream.Variable))
			c.Fail()
		}
	}
	cancel()
	<-ctx.Done()
}
コード例 #2
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestMean(c *C) {
	q, err := Parse("mean by (xyz) (/test{host=a}, /test{host=b})")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Variable[0]), Equals, "/test{host=a}")
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Variable[1]), Equals, "/test{host=b}")
	c.Check(query.Aggregation[0].Type, Equals, oproto.StreamAggregation_MEAN)
	c.Check(query.Aggregation[0].Label[0], Equals, "xyz")

	ch, err := q.Run(context.Background(), s.store)
	c.Assert(err, IsNil)
	output := []*oproto.ValueStream{}
	for stream := range ch {
		output = append(output, stream)
	}

	c.Check(output[0].Value[0].DoubleValue, Equals, float64((20*1+40*1)/2))
	c.Check(output[0].Value[1].DoubleValue, Equals, float64((20*2+40*2)/2))
	c.Check(output[0].Value[2].DoubleValue, Equals, float64((20*3+40*3)/2))
	c.Check(output[0].Value[3].DoubleValue, Equals, float64((20*4+40*4)/2))
	c.Check(output[0].Value[4].DoubleValue, Equals, float64((20*5+40*5)/2))
	c.Check(output[0].Value[5].DoubleValue, Equals, float64((20*6+40*6)/2))
	c.Check(output[0].Value[6].DoubleValue, Equals, float64((20*7+40*7)/2))
	c.Check(output[0].Value[7].DoubleValue, Equals, float64((20*8+40*8)/2))
	c.Check(output[0].Value[8].DoubleValue, Equals, float64((20*9+40*9)/2))
	c.Check(output[0].Value[9].DoubleValue, Equals, float64((20*10+40*10)/2))
	c.Check(output[0].Value[10].DoubleValue, Equals, float64((20*11+40*11)/2))
}
コード例 #3
0
func (s *MySuite) TestRatio(c *C) {
	ctx, cancel := context.WithCancel(context.Background())
	e := NewExporter(ctx, s.Client, 10*time.Second)
	r := NewRatio(e, variable.NewFromString("/test/ratio"))

	for i := 0; i < 10; i++ {
		r.Success()
	}

	for i := 0; i < 5; i++ {
		r.Failure()
	}

	// Force export
	streams := e.GetStreams()
	c.Assert(len(streams), Equals, 3)
	for _, stream := range streams {
		switch variable.ProtoToString(stream.Variable) {
		case "/test/ratio-success":
			c.Check(stream.Value[0].DoubleValue, Equals, 10.0)
		case "/test/ratio-failure":
			c.Check(stream.Value[0].DoubleValue, Equals, 5.0)
		case "/test/ratio-total":
			c.Check(stream.Value[0].DoubleValue, Equals, 15.0)
		default:
			fmt.Printf("Invalid variable %s", variable.ProtoToString(stream.Variable))
			c.Fail()
		}
	}
	cancel()
	<-ctx.Done()
}
コード例 #4
0
func (s *MySuite) TestTimer(c *C) {
	ctx, cancel := context.WithCancel(context.Background())
	e := NewExporter(ctx, s.Client, 10*time.Second)
	t := NewTimer(e, variable.NewFromString("/test/timer"))

	t.Start()
	time.Sleep(10 * time.Millisecond)
	t.Stop()

	t.Start()
	time.Sleep(10 * time.Millisecond)
	t.Stop()

	// Force export
	streams := e.GetStreams()
	c.Assert(len(streams), Equals, 2)
	for _, stream := range streams {
		switch variable.ProtoToString(stream.Variable) {
		case "/test/timer-total-count":
			c.Check((stream.Value[0].DoubleValue >= 20.0 && stream.Value[0].DoubleValue <= 25.0), Equals, true)
		case "/test/timer-overall-sum":
			c.Check(stream.Value[0].DoubleValue, Equals, 2.0)
		default:
			fmt.Printf("Invalid variable %s", variable.ProtoToString(stream.Variable))
			c.Fail()
		}
	}
	cancel()
	<-ctx.Done()
}
コード例 #5
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestAggregationOfMutations(c *C) {
	q, err := Parse("mean by (host) (rate(/test{host=a}[1200:1500], /test{host=b}[1200:1500]))")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(query.Aggregation[0].Type, Equals, oproto.StreamAggregation_MEAN)
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Type, Equals, oproto.StreamMutation_RATE)
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0]), Equals, "/test{host=a}[1200:1500]")
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1]), Equals, "/test{host=b}[1200:1500]")
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0].MinTimestamp, Equals, int64(1200))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0].MaxTimestamp, Equals, int64(1500))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1].MinTimestamp, Equals, int64(1200))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1].MaxTimestamp, Equals, int64(1500))
}
コード例 #6
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestAggregationOfPercentile(c *C) {
	q, err := Parse("percentile(90) by (host) (rate(/test{host=a}[1200:1500], /test{host=b}[1200:1500]))")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(query.Aggregation[0].Type, Equals, oproto.StreamAggregation_PERCENTILE)
	c.Check(query.Aggregation[0].Param, Equals, 90.0)
	c.Check(query.Aggregation[0].Label[0], Equals, "host")
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0]), Equals, "/test{host=a}[1200:1500]")
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1]), Equals, "/test{host=b}[1200:1500]")
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0].MinTimestamp, Equals, int64(1200))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[0].MaxTimestamp, Equals, int64(1500))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1].MinTimestamp, Equals, int64(1200))
	c.Check(query.Aggregation[0].Query[0].Mutation[0].Query.Variable[1].MaxTimestamp, Equals, int64(1500))
}
コード例 #7
0
ファイル: block.go プロジェクト: dparrish/openinstrument
// AddStream adds a new stream to the unlogged streams list.
// The stream is not flushed to disk until block.Flush() is called (which happens regularly).
func (block *Block) AddStream(stream *oproto.ValueStream) {
	block.newStreamsLock.Lock()
	defer block.newStreamsLock.Unlock()
	v := variable.ProtoToString(stream.Variable)
	for _, existingstream := range block.NewStreams {
		if variable.ProtoToString(existingstream.Variable) == v {
			existingstream.Value = append(existingstream.Value, stream.Value...)
			block.Block.UnloggedValues += uint32(len(stream.Value))
			return
		}
	}
	block.NewStreams = append(block.NewStreams, stream)
	block.Block.UnloggedValues += uint32(len(stream.Value))
	block.Block.UnloggedStreams++
}
コード例 #8
0
ファイル: block_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestGetStreamForVariable(c *C) {
	block := NewBlock(context.Background(), "/test/foo", "", s.dataDir)
	for v := 0; v < 10; v++ {
		varName := fmt.Sprintf("/test/bar%d", v)
		block.LogStreams[varName] = &oproto.ValueStream{
			Variable: &oproto.StreamVariable{Name: varName},
			Value:    []*oproto.Value{},
		}
		for i := 0; i < 100; i++ {
			block.LogStreams[varName].Value = append(block.LogStreams[varName].Value,
				&oproto.Value{DoubleValue: float64(i)})
		}
	}
	c.Assert(block.Compact(context.Background()), IsNil)
	found := false
	for _, index := range block.Block.Header.Index {
		cv := variable.NewFromProto(index.Variable)
		if cv.String() != "/test/bar7" {
			continue
		}
		stream := block.getIndexedStream(context.Background(), index)
		c.Assert(variable.ProtoToString(stream.Variable), Equals, "/test/bar7")
		found = true
		break
	}
	c.Assert(found, Equals, true)
}
コード例 #9
0
ファイル: block.go プロジェクト: dparrish/openinstrument
func (block *Block) RunLengthEncodeStreams(ctx context.Context, streams map[string]*oproto.ValueStream) map[string]*oproto.ValueStream {
	// Run-length encode all streams in parallel
	var sl sync.Mutex
	var outputValues int
	wg := &sync.WaitGroup{}
	newStreams := make(map[string]*oproto.ValueStream, 0)
	for _, stream := range streams {
		wg.Add(1)
		go func(stream *oproto.ValueStream) {
			defer wg.Done()
			// Sort values by timestamp
			value.By(func(a, b *oproto.Value) bool { return a.Timestamp < b.Timestamp }).Sort(stream.Value)

			// Run-length encode values
			stream = rle.Encode(stream)
			sl.Lock()
			newStreams[variable.ProtoToString(stream.Variable)] = stream
			outputValues += len(stream.Value)
			sl.Unlock()
		}(stream)
	}
	wg.Wait()

	openinstrument.Logf(ctx, "Run-length encoded %d streams to %d", len(newStreams), outputValues)

	return newStreams
}
コード例 #10
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestMutation(c *C) {
	q, err := Parse("rate(/test{host=a})")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(query.Mutation[0].Type, Equals, oproto.StreamMutation_RATE)
	c.Check(variable.ProtoToString(query.Mutation[0].Query.Variable[0]), Equals, "/test{host=a}")

	ch, err := q.Run(context.Background(), s.store)
	c.Assert(err, IsNil)
	numStreams := 0
	for stream := range ch {
		c.Check(variable.ProtoToString(stream.Variable), Equals, "/test{host=a}")
		c.Check(len(stream.Value), Equals, 10)
		numStreams++
	}
	c.Check(numStreams, Equals, 1)
}
コード例 #11
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestVariableNoLabels(c *C) {
	q, err := Parse("/test{}")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Variable[0]), Equals, "/test")
	c.Check(query.Variable[0].MinTimestamp, Equals, int64(0))
	c.Check(query.Variable[0].MaxTimestamp, Equals, int64(0))

	ch, err := q.Run(context.Background(), s.store)
	c.Assert(err, IsNil)
	numStreams := 0
	for stream := range ch {
		c.Check(variable.ProtoToString(stream.Variable), Equals, "/test")
		c.Check(len(stream.Value), Equals, 11)
		numStreams++
	}
	c.Check(numStreams, Equals, 1)
}
コード例 #12
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestPercentile(c *C) {
	q, err := Parse("percentile(20) by (host) (/test{host=a})")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Aggregation[0].Query[0].Variable[0]), Equals, "/test{host=a}")
	c.Check(query.Aggregation[0].Type, Equals, oproto.StreamAggregation_PERCENTILE)
	c.Check(query.Aggregation[0].Param, Equals, 20.0)
	c.Check(query.Aggregation[0].Label[0], Equals, "host")
}
コード例 #13
0
ファイル: block.go プロジェクト: dparrish/openinstrument
func (block *Block) AddStreams(c <-chan *oproto.ValueStream) {
	block.newStreamsLock.Lock()
	defer block.newStreamsLock.Unlock()
CHAN:
	for stream := range c {
		v := variable.ProtoToString(stream.Variable)

		for _, existingstream := range block.NewStreams {
			if variable.ProtoToString(existingstream.Variable) == v {
				existingstream.Value = append(existingstream.Value, stream.Value...)
				block.Block.UnloggedValues += uint32(len(stream.Value))
				continue CHAN
			}
		}

		block.NewStreams = append(block.NewStreams, stream)
		block.Block.UnloggedValues += uint32(len(stream.Value))
		block.Block.UnloggedStreams++
	}
}
コード例 #14
0
func (s *MySuite) TestFloat(c *C) {
	ctx, cancel := context.WithCancel(context.Background())
	e := NewExporter(ctx, s.Client, 10*time.Second)
	f := NewFloat(e, variable.NewFromString("/test/float"))
	f.Set(100.0)

	// Force export
	streams := e.GetStreams()
	c.Assert(len(streams), Equals, 1)
	c.Check(variable.ProtoToString(streams[0].Variable), Equals, "/test/float")
	c.Check(streams[0].Value[0].DoubleValue, Equals, 100.0)
	cancel()
	<-ctx.Done()
}
コード例 #15
0
ファイル: datastore.go プロジェクト: dparrish/openinstrument
// Writer builds a channel that can accept ValueStreams for writing to the datastore.
// Any ValueStreams written to this channel will eventually be flushed to disk,
// but they will be immediately available for use.
// The writes to disk are not guaranteed until Flush is called.
func (ds *Datastore) Writer(ctx context.Context) chan<- *oproto.ValueStream {
	in := make(chan *oproto.ValueStream, 10)
	go func() {
		for stream := range in {
			// Write this stream
			varName := variable.ProtoToString(stream.Variable)
			if block := ds.findBlock(ctx, varName); block != nil {
				//openinstrument.Logf(ctx, "Writing stream for variable %s to block %s", varName, block.ID())
				block.AddStream(stream)
			} else {
				openinstrument.Logf(ctx, "Unable to find block to write variable %s", varName)
			}
		}
	}()
	return in
}
コード例 #16
0
ファイル: rpc_server.go プロジェクト: dparrish/openinstrument
func (s *server) LookupBlock(ctx context.Context, request *oproto.LookupBlockRequest) (*oproto.LookupBlockResponse, error) {
	if request.BlockId != "" {
		block, err := s.ds.GetBlock(request.BlockId, "")
		if err != nil {
			return nil, err
		}
		return &oproto.LookupBlockResponse{Block: block.ToProto()}, nil
	}
	v := variable.ProtoToString(request.Variable)
	for _, block := range s.ds.Blocks() {
		if block.EndKey() >= v {
			return &oproto.LookupBlockResponse{Block: block.ToProto()}, nil
		}
	}
	return &oproto.LookupBlockResponse{}, nil
}
コード例 #17
0
func InspectVariable(ctx context.Context, ds *datastore.Datastore, w http.ResponseWriter, req *http.Request) {
	t, err := template.ParseFiles(fmt.Sprintf("%s/inspect_variable.html", *templatePath))
	if err != nil {
		openinstrument.Logf(ctx, "Couldn't find template file: %s", err)
		return
	}
	type varInfo struct {
		Name           string
		FirstTimestamp time.Time
		LastTimestamp  time.Time
	}
	p := struct {
		Title     string
		Query     string
		Variables []varInfo
	}{
		Title:     "Inspect Variable",
		Query:     req.FormValue("q"),
		Variables: make([]varInfo, 0),
	}

	if p.Query == "" {
		w.WriteHeader(404)
		fmt.Fprintf(w, "Specify q=")
		return
	}

	v := variable.NewFromString(p.Query)
	c := ds.Reader(ctx, v)
	for stream := range c {
		lt := stream.Value[len(stream.Value)-1].EndTimestamp
		if lt == 0 {
			lt = stream.Value[len(stream.Value)-1].Timestamp
		}
		p.Variables = append(p.Variables, varInfo{
			Name:           variable.ProtoToString(stream.Variable),
			FirstTimestamp: time.Unix(int64(stream.Value[0].Timestamp/1000), 0),
			LastTimestamp:  time.Unix(int64(lt/1000), 0),
		})
	}

	err = t.Execute(w, p)
	if err != nil {
		log.Println(err)
	}
}
コード例 #18
0
ファイル: datastore.go プロジェクト: dparrish/openinstrument
func (ds *Datastore) readBlockLog(ctx context.Context, filename string) {
	block := NewBlock(ctx, "", BlockIDFromFilename(filename), ds.Path)

	file, err := protofile.Read(block.logFilename())
	if err != nil {
		openinstrument.Logf(ctx, "Error opening proto log file %s: %s", block.logFilename(), err)
	}
	defer file.Close()

	// Read all the streams from the log file
	reader := file.ValueStreamReader(ctx, 100)
	for stream := range reader {
		varName := variable.ProtoToString(stream.Variable)
		if varName > block.EndKey() {
			block.Block.EndKey = varName
		}
		locker := block.LogWriteLocker()
		locker.Lock()
		existingstream, found := block.LogStreams[varName]
		if found {
			existingstream.Value = append(existingstream.Value, stream.Value...)
		} else {
			block.LogStreams[varName] = stream
		}
		locker.Unlock()
	}

	if func() *Block {
		for _, existingblock := range ds.Blocks() {
			if existingblock.ID() == block.ID() {
				locker := existingblock.LogWriteLocker()
				locker.Lock()
				existingblock.LogStreams = block.LogStreams
				locker.Unlock()
				// Update cached number of streams and values
				existingblock.UpdateLoggedCount()
				return existingblock
			}
		}
		return nil
	}() == nil {
		// There is no existing block file for this log.
		block.UpdateLoggedCount()
		ds.insertBlock(ctx, block)
	}
}
コード例 #19
0
ファイル: block.go プロジェクト: dparrish/openinstrument
func (block *Block) Flush() error {
	block.newStreamsLock.Lock()
	defer block.newStreamsLock.Unlock()

	if len(block.NewStreams) == 0 {
		return nil
	}

	block.logLock.Lock()
	defer block.logLock.Unlock()

	// There are streams that need to be flushed to disk
	file, err := protofile.Write(block.logFilename())
	if err != nil {
		return err
	}
	defer file.Close()
	for _, stream := range block.NewStreams {
		n, err := file.Write(stream)
		if err != nil || n < 1 {
			return err
		}
		varName := variable.ProtoToString(stream.Variable)
		existingstream, found := block.LogStreams[varName]
		if found {
			existingstream.Value = append(existingstream.Value, stream.Value...)
		} else {
			block.LogStreams[varName] = stream
		}
	}
	block.NewStreams = make([]*oproto.ValueStream, 0)
	block.Block.LoggedStreams += block.Block.UnloggedStreams
	block.Block.LoggedValues += block.Block.UnloggedValues
	block.Block.UnloggedStreams = uint32(0)
	block.Block.UnloggedValues = uint32(0)
	block.UpdateSize()

	return nil
}
コード例 #20
0
ファイル: rpc_server.go プロジェクト: dparrish/openinstrument
func (s *server) List(ctx context.Context, request *oproto.ListRequest) (*oproto.ListResponse, error) {
	response := &oproto.ListResponse{
		Timer: make([]*oproto.LogMessage, 0),
	}

	requestVariable := variable.NewFromProto(request.Prefix)
	if len(requestVariable.Variable) == 0 {
		return nil, fmt.Errorf("No variable specified")
	}

	// Retrieve all variables and store the names in a map for uniqueness
	timer := openinstrument.NewTimer(ctx, "retrieve variables")
	vars := make(map[string]*oproto.StreamVariable)
	if requestVariable.MinTimestamp == 0 {
		// Get the last day
		requestVariable.MinTimestamp = -86400000
	}
	for stream := range s.ds.Reader(ctx, requestVariable) {
		if request.MaxVariables == 0 || len(vars) < int(request.MaxVariables) {
			vars[variable.ProtoToString(stream.Variable)] = stream.Variable
		}
	}
	timer.Stop()

	// Build the response out of the map
	timer = openinstrument.NewTimer(ctx, "construct response")
	response.Variable = make([]*oproto.StreamVariable, 0)
	for _, variable := range vars {
		response.Variable = append(response.Variable, variable)
	}
	response.Success = true
	timer.Stop()

	log.Printf("Timers: %s", openinstrument.GetLog(ctx))
	return response, nil
}
コード例 #21
0
ファイル: datastore.go プロジェクト: dparrish/openinstrument
// SplitBlock splits a single block into multiple (usually 2) smaller blocks.
// The new blocks' contents are immedately written to disk and reopened by the Datatstore.
// The old block is removed from disk once the new contents are available.
// This will block writes to a block for the duration of the reindexing.
func (ds *Datastore) SplitBlock(ctx context.Context, block *Block) (*Block, *Block, error) {
	defer block.UpdateIndexedCount()
	defer block.UpdateLoggedCount()
	defer block.UpdateUnloggedCount()

	// Compact the block before continuing, to make sure everything is flushed to disk
	block.Compact(ctx)

	// Work out the optimal split point
	splitPoint, leftEndKey := block.GetOptimalSplitPoint(ctx)
	if splitPoint == 0 {
		return nil, nil, fmt.Errorf("Could not split block %s: not enough streams", block)
	}
	openinstrument.Logf(ctx, "Calculated optimal split point at %d (%s)", splitPoint, leftEndKey)

	// Read in the whole block
	leftBlock := NewBlock(ctx, leftEndKey, "", ds.Path)
	leftStreams := make(map[string]*oproto.ValueStream)
	rightStreams := make(map[string]*oproto.ValueStream)

	streams, err := block.GetIndexedStreams(ctx)
	if err != nil {
		return nil, nil, fmt.Errorf("Couldn't read old block file: %s", err)
	}

	var leftError, rightError error
	func() {
		locker := block.LogWriteLocker()
		locker.Lock()
		defer locker.Unlock()

		for stream := range streams {
			varName := variable.ProtoToString(stream.Variable)
			if varName <= leftBlock.EndKey() {
				leftStreams[varName] = stream
			} else {
				rightStreams[varName] = stream
			}
		}

		wg := new(sync.WaitGroup)
		wg.Add(2)
		go func() { leftError = leftBlock.Write(ctx, leftStreams); wg.Done() }()
		go func() { rightError = block.Write(ctx, rightStreams); wg.Done() }()
		wg.Wait()
	}()

	if leftError != nil {
		return nil, nil, fmt.Errorf("Error writing left block: %s", leftError)
	}
	if rightError != nil {
		return nil, nil, fmt.Errorf("Error writing right block: %s", rightError)
	}

	ds.insertBlock(ctx, leftBlock)
	defer leftBlock.UpdateIndexedCount()
	defer leftBlock.UpdateLoggedCount()
	defer leftBlock.UpdateUnloggedCount()

	openinstrument.Logf(ctx, "Split complete, left contains %d streams, right contains %d", len(leftStreams), len(rightStreams))
	return leftBlock, block, nil
}
コード例 #22
0
ファイル: query.go プロジェクト: dparrish/openinstrument
func RunQuery(ctx context.Context, query *oproto.Query, store datastore.ReadableStore) (chan *oproto.ValueStream, error) {
	log.Printf("Running query %v", query)
	output := make(chan *oproto.ValueStream, 100)
	go func() {
		defer close(output)
		for _, v := range query.Variable {
			log.Printf("Returning variable %s", variable.ProtoToString(v))
			for stream := range store.Reader(ctx, variable.NewFromProto(v)) {
				if stream != nil {
					outputStream := &oproto.ValueStream{Variable: stream.Variable}
					stv := variable.NewFromProto(stream.Variable)
					for _, v := range stream.Value {
						if stv.TimestampInsideRange(v.Timestamp) {
							outputStream.Value = append(outputStream.Value, v)
						}
					}
					output <- outputStream
				}
			}
		}
		for _, child := range query.Aggregation {
			log.Printf("Running child aggregation")
			input := []*oproto.ValueStream{}
			for _, q := range child.Query {
				o, err := RunQuery(ctx, q, store)
				if err != nil {
					return
				}
				for stream := range o {
					input = append(input, stream)
				}
			}
			log.Printf("Child aggregation returned output")
			o := []*oproto.ValueStream{}
			switch child.Type {
			case oproto.StreamAggregation_NONE:
				o = input
			case oproto.StreamAggregation_MEAN:
				o = aggregations.Mean(child.Label, input)
			case oproto.StreamAggregation_MAX:
				o = aggregations.Max(child.Label, input)
			case oproto.StreamAggregation_MIN:
				o = aggregations.Min(child.Label, input)
			case oproto.StreamAggregation_MEDIAN:
				o = aggregations.Median(child.Label, input)
			case oproto.StreamAggregation_SUM:
				o = aggregations.Sum(child.Label, input)
			case oproto.StreamAggregation_STDDEV:
				o = aggregations.StdDev(child.Label, input)
			case oproto.StreamAggregation_PERCENTILE:
				o = aggregations.Percentile(child.Label, child.Param, input)
			}
			for _, stream := range o {
				//log.Println(openinstrument.ProtoText(stream))
				output <- stream
			}
		}
		for _, child := range query.Mutation {
			log.Printf("Running child mutation")
			input, err := RunQuery(ctx, child.Query, store)
			if err != nil {
				log.Printf("Error in child mutation: %s", err)
				return
			}
			for stream := range input {
				var outStream *oproto.ValueStream
				switch child.Type {
				case oproto.StreamMutation_MEAN:
					outStream = mutations.Mean(stream)
				case oproto.StreamMutation_INTERPOLATE:
					outStream = mutations.Interpolate(uint64(child.Param), stream)
				case oproto.StreamMutation_MIN:
					outStream = mutations.Min(uint64(child.Param), stream)
				case oproto.StreamMutation_MAX:
					outStream = mutations.Max(uint64(child.Param), stream)
				case oproto.StreamMutation_FIRST:
					outStream = mutations.First(uint64(child.Param), stream)
				case oproto.StreamMutation_LAST:
					outStream = mutations.Last(uint64(child.Param), stream)
				case oproto.StreamMutation_RATE:
					outStream = mutations.Rate(stream)
				case oproto.StreamMutation_ROOT:
					outStream = mutations.Root(child.Param, stream)
				case oproto.StreamMutation_POWER:
					outStream = mutations.Power(child.Param, stream)
				case oproto.StreamMutation_ADD:
					outStream = mutations.Add(child.Param, stream)
				case oproto.StreamMutation_MULTIPLY:
					outStream = mutations.Multiply(child.Param, stream)
				case oproto.StreamMutation_RATE_SIGNED:
					outStream = mutations.SignedRate(stream)
				case oproto.StreamMutation_MOVING_AVERAGE:
					outStream = mutations.MovingAverage(uint64(child.Param), stream)
				}
				if outStream == nil {
					log.Printf("No stream returned from mutation")
					continue
				}
				outStream.Variable = stream.Variable
				output <- outStream
			}
		}
	}()
	return output, nil
}
コード例 #23
0
ファイル: block.go プロジェクト: dparrish/openinstrument
func (block *Block) GetOptimalSplitPoint(ctx context.Context) (int, string) {
	keys := make(map[string]int, 0)
	func() {
		for _, index := range block.Block.Header.Index {
			keys[variable.ProtoToString(index.Variable)] = int(index.NumValues)
		}
		for _, stream := range block.GetLogStreams() {
			v := variable.ProtoToString(stream.Variable)
			_, ok := keys[v]
			if !ok {
				keys[v] = len(stream.Value)
			} else {
				keys[v] += len(stream.Value)
			}
		}
		block.newStreamsLock.RLocker().Lock()
		defer block.newStreamsLock.RLocker().Unlock()
		for _, stream := range block.NewStreams {
			v := variable.ProtoToString(stream.Variable)
			_, ok := keys[v]
			if !ok {
				keys[v] = len(stream.Value)
			} else {
				keys[v] += len(stream.Value)
			}
		}
	}()
	if len(keys) < 2 {
		return 0, ""
	}
	var sortedKeys []string
	for key := range keys {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)

	// Look for the split point where there are closest to an equal number of values on both sides
	moved := 0
	lastDifference := 0
	splitPoint := len(sortedKeys) / 2
	for {
		leftEndKey := sortedKeys[splitPoint-1]
		leftCount := 0
		rightCount := 0
		for _, key := range sortedKeys {
			if key <= leftEndKey {
				leftCount += keys[key]
			} else {
				rightCount += keys[key]
			}
		}
		difference := rightCount - leftCount
		if splitPoint == 1 || splitPoint == len(keys)-1 {
			// Can't move any further
			break
		}
		if difference == 0 {
			// Exact split
			break
		}
		if lastDifference != 0 && math.Abs(float64(lastDifference)) < math.Abs(float64(difference)) {
			// Last position was closer
			if moved < 0 {
				// The position directly to the right is the best
				splitPoint++
				break
			} else {
				// The position directly to the left is the best
				splitPoint--
				break
			}
		}
		if difference < 0 {
			splitPoint--
			moved = -1
			lastDifference = difference
		} else {
			splitPoint++
			moved = 1
			lastDifference = difference
		}
	}
	return splitPoint, sortedKeys[splitPoint-1]
}
コード例 #24
0
ファイル: block.go プロジェクト: dparrish/openinstrument
func (block *Block) Compact(ctx context.Context) error {
	openinstrument.Logf(ctx, "Compacting block %s\n", block)
	startTime := time.Now()

	// Update cached number of streams and values
	defer block.UpdateIndexedCount()
	defer block.UpdateLoggedCount()
	defer block.UpdateUnloggedCount()

	block.protoLock.Lock()
	defer block.protoLock.Unlock()
	block.Block.State = oproto.Block_COMPACTING
	block.compactStartTime = time.Now()

	block.newStreamsLock.Lock()
	defer block.newStreamsLock.Unlock()

	block.logLock.Lock()
	defer block.logLock.Unlock()

	streams := make(map[string]*oproto.ValueStream, 0)

	// Apply the retention policy during compaction
	p, err := store_config.Get().GetRetentionPolicy(ctx)
	if err != nil {
		return fmt.Errorf("Error getting retention policy from config store: %s", err)
	}
	policy := retentionpolicy.New(&p)
	endKey := ""

	appendValues := func(stream *oproto.ValueStream) {
		if stream.Variable == nil {
			openinstrument.Logf(ctx, "Skipping reading stream that contains no variable")
			return
		}
		varName := variable.ProtoToString(stream.Variable)
		out := policy.Apply(stream)
		if len(out.Value) == 0 {
			//openinstrument.Logf(ctx, "Dropping stream for variable %s", varName)
			return
		}
		outstream, found := streams[varName]
		if found {
			outstream.Value = append(outstream.Value, stream.Value...)
		} else {
			streams[varName] = stream
		}
		if varName > endKey {
			endKey = varName
		}
	}

	// Append logged streams
	for _, stream := range block.LogStreams {
		appendValues(stream)
	}
	openinstrument.Logf(ctx, "Block log contains %d streams", len(streams))

	// Append indexed streams
	reader, err := block.GetIndexedStreams(ctx)
	if err != nil {
		openinstrument.Logf(ctx, "Unable to read block: %s", err)
	} else {
		for stream := range reader {
			appendValues(stream)
		}
		openinstrument.Logf(ctx, "Compaction read block containing %d streams", len(streams))
	}

	// Append unlogged (new) streams
	if len(block.NewStreams) > 0 {
		for _, stream := range block.NewStreams {
			appendValues(stream)
		}
		openinstrument.Logf(ctx, "Compaction added %d unlogged streams, total: %d streams", len(block.NewStreams), len(streams))
	}

	// The end key may have changed if streams have been dropped
	block.Block.EndKey = endKey

	if err = block.Write(ctx, streams); err != nil {
		openinstrument.Logf(ctx, "Error writing: %s", err)
		return err
	}

	// Delete the log file
	os.Remove(block.logFilename())
	openinstrument.Logf(ctx, "Deleted log file %s", block.logFilename())
	block.LogStreams = make(map[string]*oproto.ValueStream)
	block.NewStreams = make([]*oproto.ValueStream, 0)

	block.compactEndTime = time.Now()
	block.Block.State = oproto.Block_LIVE
	block.UpdateSize()
	openinstrument.Logf(ctx, "Finished compaction of %s in %v", block, time.Since(startTime))

	return nil
}
コード例 #25
0
ファイル: block.go プロジェクト: dparrish/openinstrument
// Write writes a map of ValueStreams to a single block file on disk.
// The values inside each ValueStream will be sorted and run-length-encoded before writing.
func (block *Block) Write(ctx context.Context, streams map[string]*oproto.ValueStream) error {
	// Build the header with a 0-index for each variable
	block.Block.Header.Index = []*oproto.BlockHeaderIndex{}
	block.Block.Header.EndKey = ""
	block.Block.Header.StartTimestamp = 0
	block.Block.Header.EndTimestamp = 0
	streams = block.RunLengthEncodeStreams(ctx, streams)
	for v, stream := range streams {
		if v > block.Block.Header.EndKey {
			block.Block.Header.EndKey = v
		}
		// Add this stream to the index
		block.Block.Header.Index = append(block.Block.Header.Index, &oproto.BlockHeaderIndex{
			Variable:     stream.Variable,
			Offset:       uint64(1), // This must be set non-zero so that the protobuf marshals it to non-empty
			MinTimestamp: stream.Value[0].Timestamp,
			MaxTimestamp: stream.Value[len(stream.Value)-1].Timestamp,
			NumValues:    uint32(len(stream.Value)),
		})

		if block.Block.Header.StartTimestamp == 0 || stream.Value[0].Timestamp < block.Block.Header.StartTimestamp {
			block.Block.Header.StartTimestamp = stream.Value[0].Timestamp
		}
		if stream.Value[len(stream.Value)-1].Timestamp > block.Block.Header.EndTimestamp {
			block.Block.Header.EndTimestamp = stream.Value[len(stream.Value)-1].Timestamp
		}
	}

	// Start writing to the new block file
	newfilename := fmt.Sprintf("%s.new.%d", block.Filename(), os.Getpid())
	newfile, err := protofile.Write(newfilename)
	if err != nil {
		newfile.Close()
		return fmt.Errorf("Can't write to %s: %s\n", newfilename, err)
	}
	newfile.Write(block.Block.Header)
	blockEnd := newfile.Tell()

	// Write all the ValueStreams
	indexPos := make(map[string]uint64)
	var outValues uint32
	for _, stream := range streams {
		indexPos[variable.ProtoToString(stream.Variable)] = uint64(newfile.Tell())
		newfile.Write(stream)
		outValues += uint32(len(stream.Value))
	}

	// Update the offsets in the header, now that all the data has been written
	for _, index := range block.Block.Header.Index {
		index.Offset = indexPos[variable.ProtoToString(index.Variable)]
	}

	newfile.WriteAt(0, block.Block.Header)
	if blockEnd < newfile.Tell() {
		// Sanity check, just in case goprotobuf breaks something again
		newfile.Close()
		os.Remove(newfilename)
		log.Fatalf("Error writing block file %s, header overwrote data", newfilename)
	}

	newfile.Sync()
	newfile.Close()

	block.UpdateIndexedCount()

	openinstrument.Logf(ctx, "Wrote %d streams / %d values to %s", len(streams), outValues, newfilename)
	openinstrument.Logf(ctx, "Block log contains %d stream", len(block.Block.Header.Index))

	// Rename the temporary file into place
	if err := os.Rename(newfilename, block.Filename()); err != nil {
		return fmt.Errorf("Error renaming: %s", err)
	}

	return nil
}
コード例 #26
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestVariableNoLabelsOrBraces(c *C) {
	q, err := Parse("/test")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Variable[0]), Equals, "/test")
}
コード例 #27
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestVariableTwoLabels(c *C) {
	q, err := Parse("/test{x=y,host=a}")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Variable[0]), Equals, "/test{host=a,x=y}")
}
コード例 #28
0
ファイル: query_test.go プロジェクト: dparrish/openinstrument
func (s *MySuite) TestLabelWildcard(c *C) {
	q, err := Parse("/test{host=*}")
	c.Assert(err, IsNil)
	query := q.query
	c.Check(variable.ProtoToString(query.Variable[0]), Equals, "/test{host=*}")
}
コード例 #29
0
func Query(ctx context.Context, ds *datastore.Datastore, w http.ResponseWriter, req *http.Request) {
	query := req.FormValue("q")
	showValues := req.FormValue("v") == "1"
	type Result struct {
		Variable string          `json:"name"`
		Values   [][]interface{} `json:"values"`
	}

	var duration *time.Duration
	requestVariable := variable.NewFromString(query)
	if req.FormValue("d") != "" {
		d, err := time.ParseDuration(req.FormValue("d"))
		if err != nil {
			w.WriteHeader(401)
			fmt.Fprintf(w, "Invalid duration")
			return
		}
		duration = &d
		requestVariable.MinTimestamp = int64(time.Now().UnixNano()-d.Nanoseconds()) / 1000000
	}

	if query == "" {
		w.WriteHeader(401)
		fmt.Fprintf(w, "Specify q=")
		return
	}

	results := make([]Result, 0)

	for stream := range ds.Reader(ctx, requestVariable) {
		r := Result{
			Variable: variable.ProtoToString(stream.Variable),
		}
		if !showValues {
			results = append(results, r)
			continue
		}
		r.Values = make([][]interface{}, 0)

		if duration == nil {
			// Latest value only
			if len(stream.Value) > 0 {
				v := stream.Value[len(stream.Value)-1]
				r.Values = append(r.Values, []interface{}{v.Timestamp, v.DoubleValue})
			}
		} else {
			// All values over a specific time period
			for _, v := range stream.Value {
				if requestVariable.MinTimestamp == 0 || requestVariable.MinTimestamp > int64(v.Timestamp) {
					r.Values = append(r.Values, []interface{}{v.Timestamp, v.DoubleValue})
				}
			}
		}
		results = append(results, r)
	}

	b, err := json.Marshal(results)
	if err != nil {
		w.WriteHeader(500)
		fmt.Fprintf(w, "Couldn't marshal: %s", err)
		return
	}
	w.WriteHeader(200)
	w.Write(b)
}