1// Copyright 2018, OpenCensus Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16package ocgrpc
17
18import (
19	"regexp"
20	"strings"
21	"testing"
22
23	"go.opencensus.io/stats"
24	"go.opencensus.io/stats/view"
25)
26
27var colSep = regexp.MustCompile(`\s*\|\s*`)
28
29func TestSpecClientMeasures(t *testing.T) {
30	spec := `
31| Measure name                             | Unit | Description                                                                                   |
32|------------------------------------------|------|-----------------------------------------------------------------------------------------------|
33| grpc.io/client/sent_messages_per_rpc     | 1    | Number of messages sent in the RPC (always 1 for non-streaming RPCs).                       |
34| grpc.io/client/sent_bytes_per_rpc        | By   | Total bytes sent across all request messages per RPC.                                       |
35| grpc.io/client/received_messages_per_rpc | 1    | Number of response messages received per RPC (always 1 for non-streaming RPCs).           |
36| grpc.io/client/received_bytes_per_rpc    | By   | Total bytes received across all response messages per RPC.                                  |
37| grpc.io/client/roundtrip_latency         | ms   | Time between first byte of request sent to last byte of response received, or terminal error. |
38| grpc.io/client/server_latency            | ms   | Propagated from the server and should have the same value as "grpc.io/server/latency".        |`
39
40	lines := strings.Split(spec, "\n")[3:]
41	type measureDef struct {
42		name string
43		unit string
44		desc string
45	}
46	measureDefs := make([]measureDef, 0, len(lines))
47	for _, line := range lines {
48		cols := colSep.Split(line, -1)[1:]
49		if len(cols) < 3 {
50			t.Fatalf("Invalid config line %#v", cols)
51		}
52		measureDefs = append(measureDefs, measureDef{cols[0], cols[1], cols[2]})
53	}
54
55	gotMeasures := []stats.Measure{
56		ClientSentMessagesPerRPC,
57		ClientSentBytesPerRPC,
58		ClientReceivedMessagesPerRPC,
59		ClientReceivedBytesPerRPC,
60		ClientRoundtripLatency,
61		ClientServerLatency,
62	}
63
64	if got, want := len(gotMeasures), len(measureDefs); got != want {
65		t.Fatalf("len(gotMeasures) = %d; want %d", got, want)
66	}
67
68	for i, m := range gotMeasures {
69		defn := measureDefs[i]
70		if got, want := m.Name(), defn.name; got != want {
71			t.Errorf("Name = %q; want %q", got, want)
72		}
73		if got, want := m.Unit(), defn.unit; got != want {
74			t.Errorf("%q: Unit = %q; want %q", defn.name, got, want)
75		}
76		if got, want := m.Description(), defn.desc; got != want {
77			t.Errorf("%q: Description = %q; want %q", defn.name, got, want)
78		}
79	}
80}
81
82func TestSpecClientViews(t *testing.T) {
83	defaultViewsSpec := `
84| View name                             | Measure suffix         | Aggregation  | Tags                         |
85|---------------------------------------|------------------------|--------------|------------------------------|
86| grpc.io/client/sent_bytes_per_rpc     | sent_bytes_per_rpc     | distribution | client_method                |
87| grpc.io/client/received_bytes_per_rpc | received_bytes_per_rpc | distribution | client_method                |
88| grpc.io/client/roundtrip_latency      | roundtrip_latency      | distribution | client_method                |
89| grpc.io/client/completed_rpcs         | roundtrip_latency      | count        | client_method, client_status |`
90
91	extraViewsSpec := `
92| View name                                | Measure suffix            | Aggregation  | Tags suffix   |
93|------------------------------------------|---------------------------|--------------|---------------|
94| grpc.io/client/sent_messages_per_rpc     | sent_messages_per_rpc     | distribution | client_method |
95| grpc.io/client/received_messages_per_rpc | received_messages_per_rpc | distribution | client_method |
96| grpc.io/client/server_latency            | server_latency            | distribution | client_method |`
97
98	lines := strings.Split(defaultViewsSpec, "\n")[3:]
99	lines = append(lines, strings.Split(extraViewsSpec, "\n")[3:]...)
100	type viewDef struct {
101		name          string
102		measureSuffix string
103		aggregation   string
104		tags          string
105	}
106	viewDefs := make([]viewDef, 0, len(lines))
107	for _, line := range lines {
108		cols := colSep.Split(line, -1)[1:]
109		if len(cols) < 4 {
110			t.Fatalf("Invalid config line %#v", cols)
111		}
112		viewDefs = append(viewDefs, viewDef{cols[0], cols[1], cols[2], cols[3]})
113	}
114
115	views := DefaultClientViews
116	views = append(views, ClientSentMessagesPerRPCView, ClientReceivedMessagesPerRPCView, ClientServerLatencyView)
117
118	if got, want := len(views), len(viewDefs); got != want {
119		t.Fatalf("len(gotMeasures) = %d; want %d", got, want)
120	}
121
122	for i, v := range views {
123		defn := viewDefs[i]
124		if got, want := v.Name, defn.name; got != want {
125			t.Errorf("Name = %q; want %q", got, want)
126		}
127		if got, want := v.Measure.Name(), "grpc.io/client/"+defn.measureSuffix; got != want {
128			t.Errorf("%q: Measure.Name = %q; want %q", defn.name, got, want)
129		}
130		switch v.Aggregation.Type {
131		case view.AggTypeDistribution:
132			if got, want := "distribution", defn.aggregation; got != want {
133				t.Errorf("%q: Description = %q; want %q", defn.name, got, want)
134			}
135		case view.AggTypeCount:
136			if got, want := "count", defn.aggregation; got != want {
137				t.Errorf("%q: Description = %q; want %q", defn.name, got, want)
138			}
139		default:
140			t.Errorf("Invalid aggregation type")
141		}
142		wantTags := strings.Split(defn.tags, ", ")
143		if got, want := len(v.TagKeys), len(wantTags); got != want {
144			t.Errorf("len(TagKeys) = %d; want %d", got, want)
145		}
146		for j := range wantTags {
147			if got, want := v.TagKeys[j].Name(), "grpc_"+wantTags[j]; got != want {
148				t.Errorf("TagKeys[%d].Name() = %q; want %q", j, got, want)
149			}
150		}
151	}
152}
153