Skip to content

Commit f412066

Browse files
authored
chore: restore firelog exporter (#8555) (#8599)
This reverts commit 710963c.
1 parent 71a04dc commit f412066

File tree

10 files changed

+758
-27
lines changed

10 files changed

+758
-27
lines changed

pkg/skaffold/instrumentation/ci.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright 2020 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package instrumentation
18+
19+
var ciMap = map[string]string{
20+
"TF_BUILD": "azure-pipelines",
21+
"bamboo_buildKey": "bamboo",
22+
"BUILDKITE": "buildkite",
23+
"CIRCLECI": "circle-ci",
24+
"CIRRUS_CI": "cirrus-ci",
25+
"CODEBUILD_BUILD_ID": "code-build",
26+
"GITHUB_ACTIONS": "github-actions",
27+
"GITLAB_CI": "gitlab-ci",
28+
"HEROKU_TEST_RUN_ID": "heroku-ci",
29+
"JENKINS_URL": "jenkins",
30+
"TEAMCITY_VERSION": "team-city",
31+
"TRAVIS": "travis-ci",
32+
}

pkg/skaffold/instrumentation/export.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ func exportMetrics(ctx context.Context, filename string, meter skaffoldMeter) er
9494
}
9595
var meters []skaffoldMeter
9696
err = json.Unmarshal(b, &meters)
97+
// each meter contains around 20 datapoints, and each datapoint requires a request to firelog api
98+
// we send at most 10 meters stored in skaffold metrics as too many request may result in the firelog server returning 429.
99+
if len(meters) >= 10 {
100+
meters = meters[:10]
101+
}
97102
if err != nil {
98103
meters = []skaffoldMeter{}
99104
}
@@ -160,17 +165,13 @@ func devStdOutExporter() (sdkmetric.Exporter, error) {
160165
}
161166

162167
func createMetrics(ctx context.Context, meter skaffoldMeter) {
163-
// There is a minimum 10 second interval that metrics are allowed to upload to Cloud monitoring
168+
m := global.Meter("skaffold")
169+
164170
// A metric is uniquely identified by the metric name and the labels and corresponding values
165171
// This random number is used as a label to differentiate the metrics per user so if two users
166172
// run `skaffold build` at the same time they will both have their metrics recorded
167173
randLabel := attribute.String("randomizer", strconv.Itoa(rand.Intn(75000)))
168174

169-
m := global.Meter("skaffold")
170-
171-
// cloud monitoring only supports string type labels
172-
// cloud monitoring only supports 10 labels per metric descriptor
173-
// be careful when appending new values to this `labels` slice
174175
labels := []attribute.KeyValue{
175176
attribute.String("version", meter.Version),
176177
attribute.String("os", meter.OS),
@@ -180,15 +181,13 @@ func createMetrics(ctx context.Context, meter skaffoldMeter) {
180181
attribute.String("platform_type", meter.PlatformType),
181182
attribute.String("config_count", strconv.Itoa(meter.ConfigCount)),
182183
attribute.String("cluster_type", meter.ClusterType),
183-
}
184-
sharedLabels := []attribute.KeyValue{
184+
attribute.String("ci_cd_platform", meter.CISystem),
185185
randLabel,
186186
}
187187

188188
if allowedUser := user.IsAllowedUser(meter.User); allowedUser {
189-
sharedLabels = append(sharedLabels, attribute.String("user", meter.User))
189+
labels = append(labels, attribute.String("user", meter.User))
190190
}
191-
labels = append(labels, sharedLabels...)
192191
platformLabel := attribute.String("host_os_arch", fmt.Sprintf("%s/%s", meter.OS, meter.Arch))
193192
runCounter := NewInt64ValueRecorder(m, "launches", instrument.WithDescription("Skaffold Invocations"))
194193
runCounter.Record(ctx, 1, labels...)
@@ -197,36 +196,36 @@ func createMetrics(ctx context.Context, meter skaffoldMeter) {
197196
instrument.WithDescription("durations of skaffold commands in seconds"))
198197
durationRecorder.Record(ctx, meter.Duration.Seconds(), labels...)
199198
if meter.Command != "" {
200-
commandMetrics(ctx, meter, m, sharedLabels...)
201-
flagMetrics(ctx, meter, m, randLabel)
199+
commandMetrics(ctx, meter, m, labels...)
200+
flagMetrics(ctx, meter, m, labels...)
202201
hooksMetrics(ctx, meter, m, labels...)
203202
if doesBuild.Contains(meter.Command) {
204-
builderMetrics(ctx, meter, m, platformLabel, sharedLabels...)
203+
builderMetrics(ctx, meter, m, platformLabel, labels...)
205204
}
206205
if doesDeploy.Contains(meter.Command) {
207-
deployerMetrics(ctx, meter, m, sharedLabels...)
206+
deployerMetrics(ctx, meter, m, labels...)
208207
}
209208
if doesDeploy.Contains(meter.Command) || meter.Command == "render" {
210-
resourceSelectorMetrics(ctx, meter, m, sharedLabels...)
209+
resourceSelectorMetrics(ctx, meter, m, labels...)
211210
}
212211
}
213212

214213
if meter.ErrorCode != 0 {
215-
errorMetrics(ctx, meter, m, append(sharedLabels, platformLabel)...)
214+
errorMetrics(ctx, meter, m, append(labels, platformLabel)...)
216215
}
217216
}
218217

219-
func flagMetrics(ctx context.Context, meter skaffoldMeter, m metric.Meter, randLabel attribute.KeyValue) {
218+
func flagMetrics(ctx context.Context, meter skaffoldMeter, m metric.Meter, labels ...attribute.KeyValue) {
220219
flagCounter := NewInt64ValueRecorder(m, "flags", instrument.WithDescription("Tracks usage of enum flags"))
221220
for k, v := range meter.EnumFlags {
222-
labels := []attribute.KeyValue{
221+
l := []attribute.KeyValue{
223222
attribute.String("flag_name", k),
224223
attribute.String("flag_value", v),
225224
attribute.String("command", meter.Command),
226225
attribute.String("error", meter.ErrorCode.String()),
227-
randLabel,
228226
}
229-
flagCounter.Record(ctx, 1, labels...)
227+
l = append(l, labels...)
228+
flagCounter.Record(ctx, 1, l...)
230229
}
231230
}
232231

@@ -249,12 +248,13 @@ func commandMetrics(ctx context.Context, meter skaffoldMeter, m metric.Meter, la
249248
m := counts[iteration.Intent]
250249
m[iteration.ErrorCode]++
251250
}
251+
randomizer := attribute.String("randomizer2", strconv.Itoa(rand.Intn(75000)))
252252
for intention, errorCounts := range counts {
253253
for errorCode, count := range errorCounts {
254254
iterationCounter.Record(ctx, int64(count),
255255
append(labels,
256256
attribute.String("intent", intention),
257-
attribute.String("error", errorCode.String()),
257+
attribute.String("error", errorCode.String()), randomizer,
258258
)...)
259259
}
260260
}

pkg/skaffold/instrumentation/export_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
3232

3333
"github.com/GoogleContainerTools/skaffold/v2/fs"
34+
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/instrumentation/firelog"
3435
"github.com/GoogleContainerTools/skaffold/v2/proto/v1"
3536
"github.com/GoogleContainerTools/skaffold/v2/testutil"
3637
)
@@ -105,7 +106,7 @@ func TestExportMetrics(t *testing.T) {
105106
StartTime: startTime.Add(time.Hour * 24 * 10),
106107
Duration: time.Minute * 4,
107108
}
108-
metersBytes, _ := json.Marshal([]skaffoldMeter{buildMeter, devMeter, debugMeter})
109+
metersBytes, _ := json.Marshal([]skaffoldMeter{devMeter, debugMeter, buildMeter})
109110
fakeFS := testutil.FakeFileSystem{
110111
Files: map[string][]byte{
111112
"assets/secrets_generated/keys.json": []byte(testKey),
@@ -168,6 +169,7 @@ func TestExportMetrics(t *testing.T) {
168169

169170
fs.AssetsFS = fakeFS
170171
t.Override(&isOnline, test.isOnline)
172+
t.Override(&firelog.APIKey, "no-empty")
171173

172174
if test.isOnline {
173175
tmpFile, err := os.OpenFile(tmp.Path(openTelFilename), os.O_RDWR|os.O_CREATE, os.ModePerm)
@@ -406,6 +408,7 @@ func checkOutput(t *testutil.T, meters []skaffoldMeter, b []byte) {
406408
platform := make(map[interface{}]int)
407409
buildPlatforms := make(map[interface{}]int)
408410
cliPlatforms := make(map[interface{}]int)
411+
ciCDPlatforms := make(map[interface{}]int)
409412
nodePlatforms := make(map[interface{}]int)
410413

411414
testMaps := []map[interface{}]int{
@@ -419,6 +422,7 @@ func checkOutput(t *testutil.T, meters []skaffoldMeter, b []byte) {
419422
commandCount[meter.Command]++
420423
errorCount[meter.ErrorCode.String()]++
421424
platform[meter.PlatformType]++
425+
ciCDPlatforms[meter.CISystem]++
422426

423427
for k, v := range meter.EnumFlags {
424428
n := strings.ReplaceAll(k, "-", "_")
@@ -485,10 +489,6 @@ func checkOutput(t *testutil.T, meters []skaffoldMeter, b []byte) {
485489
}
486490
}
487491
}
488-
for _, l := range lines {
489-
fmt.Println(l.Name)
490-
fmt.Println(l.Labels)
491-
}
492492

493493
for _, l := range lines {
494494
switch l.Name {
@@ -578,6 +578,10 @@ func checkOutput(t *testutil.T, meters []skaffoldMeter, b []byte) {
578578
if v, ok := l.Labels["cli-platforms"]; ok {
579579
cliPlatforms[v]++
580580
}
581+
case "ci-cd-platforms":
582+
if v, ok := l.Labels["ci-cd-platforms"]; ok {
583+
ciCDPlatforms[v]++
584+
}
581585
default:
582586
switch {
583587
case MeteredCommands.Contains(l.Name):
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
Copyright 2020 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package firelog
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
"net/http"
24+
"os"
25+
26+
"go.opentelemetry.io/otel/attribute"
27+
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
28+
"go.opentelemetry.io/otel/sdk/metric"
29+
"go.opentelemetry.io/otel/sdk/metric/aggregation"
30+
"go.opentelemetry.io/otel/sdk/metric/metricdata"
31+
)
32+
33+
var (
34+
APIKey = ""
35+
url = fmt.Sprintf(`https://firebaselogging-pa.googleapis.com/v1/firelog/legacy/log?key=%s`, APIKey)
36+
POST = http.Post
37+
Marshal = json.Marshal
38+
)
39+
40+
type Exporter struct {
41+
}
42+
43+
func NewFireLogExporter() (metric.Exporter, error) {
44+
if APIKey == "" {
45+
// export metrics to std out if local env is set.
46+
if _, ok := os.LookupEnv("SKAFFOLD_EXPORT_TO_STDOUT"); ok {
47+
enc := json.NewEncoder(os.Stdout)
48+
enc.SetIndent("", " ")
49+
exporter, err := stdoutmetric.New(stdoutmetric.WithEncoder(enc))
50+
return exporter, err
51+
}
52+
return nil, nil
53+
}
54+
return &Exporter{}, nil
55+
}
56+
57+
// Temporality returns the Temporality to use for an instrument kind.
58+
func (e *Exporter) Temporality(ik metric.InstrumentKind) metricdata.Temporality {
59+
return metric.DefaultTemporalitySelector(ik)
60+
}
61+
62+
// Aggregation returns the Aggregation to use for an instrument kind.
63+
func (e *Exporter) Aggregation(ik metric.InstrumentKind) aggregation.Aggregation {
64+
return metric.DefaultAggregationSelector(ik)
65+
}
66+
67+
func (e *Exporter) Export(ctx context.Context, md metricdata.ResourceMetrics) error {
68+
for _, sm := range md.ScopeMetrics {
69+
for _, m := range sm.Metrics {
70+
if err := processMetrics(m); err != nil {
71+
return err
72+
}
73+
}
74+
}
75+
return nil
76+
}
77+
78+
func processMetrics(m metricdata.Metrics) error {
79+
switch a := m.Data.(type) {
80+
case metricdata.Gauge[int64]:
81+
for _, pt := range a.DataPoints {
82+
if err := sendDataPoint(m.Name, DataPointInt64(pt)); err != nil {
83+
return err
84+
}
85+
}
86+
case metricdata.Gauge[float64]:
87+
for _, pt := range a.DataPoints {
88+
if err := sendDataPoint(m.Name, DataPointFloat64(pt)); err != nil {
89+
return err
90+
}
91+
}
92+
case metricdata.Sum[int64]:
93+
for _, pt := range a.DataPoints {
94+
if err := sendDataPoint(m.Name, DataPointInt64(pt)); err != nil {
95+
return err
96+
}
97+
}
98+
case metricdata.Sum[float64]:
99+
for _, pt := range a.DataPoints {
100+
if err := sendDataPoint(m.Name, DataPointFloat64(pt)); err != nil {
101+
return err
102+
}
103+
}
104+
case metricdata.Histogram:
105+
for _, pt := range a.DataPoints {
106+
if err := sendDataPoint(m.Name, DataPointHistogram(pt)); err != nil {
107+
return err
108+
}
109+
}
110+
}
111+
return nil
112+
}
113+
114+
func sendDataPoint(name string, dp DataPoint) error {
115+
kvs := toEventMetadata(dp.attributes())
116+
kvs = append(kvs, KeyValue{Key: name, Value: dp.value()})
117+
str, err := buildProtoStr(name, kvs)
118+
if err != nil {
119+
return err
120+
}
121+
data := buildMetricData(str, dp.eventTime(), dp.upTime())
122+
123+
resp, err := POST(url, "application/json", data.newReader())
124+
if err != nil {
125+
return err
126+
}
127+
defer resp.Body.Close()
128+
if resp.StatusCode != http.StatusOK {
129+
return fmt.Errorf("one platform returned an non-success response: %d", resp.StatusCode)
130+
}
131+
132+
return err
133+
}
134+
135+
func buildMetricData(proto string, startTimeMS int64, upTimeMS int64) MetricData {
136+
return MetricData{
137+
ClientInfo: ClientInfo{ClientType: "DESKTOP"},
138+
LogSource: "CONCORD",
139+
LogEvent: LogEvent{
140+
EventTimeMS: startTimeMS,
141+
EventUptimeMS: upTimeMS,
142+
SourceExtensionJSONProto3Str: proto,
143+
},
144+
RequestTimeMS: startTimeMS,
145+
RequestUptimeMS: upTimeMS,
146+
}
147+
}
148+
149+
func buildProtoStr(name string, kvs EventMetadata) (string, error) {
150+
proto3 := SourceExtensionJSONProto3{
151+
ProjectID: "skaffold",
152+
ConsoleType: "SKAFFOLD",
153+
ClientInstallID: "",
154+
EventName: name,
155+
EventMetadata: kvs,
156+
}
157+
158+
b, err := Marshal(proto3)
159+
if err != nil {
160+
return "", fmt.Errorf("failed to marshal metricdata")
161+
}
162+
return string(b), nil
163+
}
164+
165+
func toEventMetadata(attributes attribute.Set) EventMetadata {
166+
kvs := EventMetadata{}
167+
iterator := attributes.Iter()
168+
for iterator.Next() {
169+
attr := iterator.Attribute()
170+
kv := KeyValue{
171+
Key: string(attr.Key),
172+
Value: attr.Value.Emit(),
173+
}
174+
kvs = append(kvs, kv)
175+
}
176+
return kvs
177+
}
178+
179+
func (e *Exporter) ForceFlush(ctx context.Context) error {
180+
return ctx.Err()
181+
}
182+
183+
func (e *Exporter) Shutdown(ctx context.Context) error {
184+
return ctx.Err()
185+
}

0 commit comments

Comments
 (0)