Skip to content

Commit

Permalink
Add metrics for count chains attestation for issue#1028 (#1034)
Browse files Browse the repository at this point in the history
* Add metrics for chains to fix issue#1028

* Add metrics for chains to fix issue#1028
  • Loading branch information
sudhishmk authored Jan 30, 2024
1 parent b2f32fb commit d5a5659
Show file tree
Hide file tree
Showing 18 changed files with 1,288 additions and 3 deletions.
37 changes: 37 additions & 0 deletions pkg/chains/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2024 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package chains

const (
SignedMessagesCount = "sgcount"
SignsStoredCount = "stcount"
PayloadUploadeCount = "plcount"
MarkedAsSignedCount = "mrcount"
PipelineRunSignedName = "pipelinerun_sign_created_total"
PipelineRunSignedDesc = "Total number of signed messages for pipelineruns"
PipelineRunUploadedName = "pipelinerun_payload_uploaded_total"
PipelineRunUploadedDesc = "Total number of uploaded payloads for pipelineruns"
PipelineRunStoredName = "pipelinerun_payload_stored_total"
PipelineRunStoredDesc = "Total number of stored payloads for pipelineruns"
PipelineRunMarkedName = "pipelinerun_marked_signed_total"
PipelineRunMarkedDesc = "Total number of objects marked as signed for pipelineruns"
TaskRunSignedName = "taskrun_sign_created_total"
TaskRunSignedDesc = "Total number of signed messages for taskruns"
TaskRunUploadedName = "taskrun_payload_uploaded_total"
TaskRunUploadedDesc = "Total number of uploaded payloads for taskruns"
TaskRunStoredName = "taskrun_payload_stored_total"
TaskRunStoredDesc = "Total number of stored payloads for taskruns"
TaskRunMarkedName = "taskrun_marked_signed_total"
TaskRunMarkedDesc = "Total number of objects marked as signed for taskruns"
)
21 changes: 18 additions & 3 deletions pkg/chains/signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ type Signer interface {
Sign(ctx context.Context, obj objects.TektonObject) error
}

type MetricsRecorder interface {
RecordCountMetrics(ctx context.Context, MetricType string)
}

type ObjectSigner struct {
// Backends: store payload and signature
// The keys are different storage option's name. {docdb, gcs, grafeas, oci, tekton}
// The values are the actual storage backends that will be used to store and retrieve provenance.
Backends map[string]storage.Backend
SecretPath string
Pipelineclientset versioned.Interface
// Metrics Recorder config
Recorder MetricsRecorder
}

func allSigners(ctx context.Context, sp string, cfg config.Config) map[string]signing.Signer {
Expand Down Expand Up @@ -135,7 +141,6 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
// Extract all the "things" to be signed.
// We might have a few of each type (several binaries, or images)
objects := signableType.ExtractObjects(ctx, tektonObj)

// Go through each object one at a time.
for _, obj := range objects {

Expand Down Expand Up @@ -175,6 +180,7 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
logger.Error(err)
continue
}
measureMetrics(ctx, SignedMessagesCount, o.Recorder)

// Now store those!
for _, backend := range sets.List[string](signableType.StorageBackend(cfg)) {
Expand All @@ -189,6 +195,8 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
if err := b.StorePayload(ctx, tektonObj, rawPayload, string(signature), storageOpts); err != nil {
logger.Error(err)
merr = multierror.Append(merr, err)
} else {
measureMetrics(ctx, SignsStoredCount, o.Recorder)
}
}

Expand All @@ -204,8 +212,8 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
merr = multierror.Append(merr, err)
} else {
logger.Infof("Uploaded entry to %s with index %d", cfg.Transparency.URL, *entry.LogIndex)

extraAnnotations[ChainsTransparencyAnnotation] = fmt.Sprintf("%s/api/v1/log/entries?logIndex=%d", cfg.Transparency.URL, *entry.LogIndex)
measureMetrics(ctx, PayloadUploadeCount, o.Recorder)
}
}

Expand All @@ -223,10 +231,17 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject)
if err := MarkSigned(ctx, tektonObj, o.Pipelineclientset, extraAnnotations); err != nil {
return err
}

measureMetrics(ctx, MarkedAsSignedCount, o.Recorder)
return nil
}

func measureMetrics(ctx context.Context, metrictype string, mtr MetricsRecorder) {
if mtr != nil {
mtr.RecordCountMetrics(ctx, metrictype)
}

}

func HandleRetry(ctx context.Context, obj objects.TektonObject, ps versioned.Interface, annotations map[string]string) error {
if RetryAvailable(obj) {
return AddRetry(ctx, obj, ps, annotations)
Expand Down
30 changes: 30 additions & 0 deletions pkg/pipelinerunmetrics/fake/fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright 2024 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fake

import (
"context"

"github.com/tektoncd/chains/pkg/pipelinerunmetrics"
_ "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1/pipelinerun/fake" // Make sure the fake pipelinerun informer is setup
"k8s.io/client-go/rest"
"knative.dev/pkg/injection"
)

func init() {
injection.Fake.RegisterClient(func(ctx context.Context, _ *rest.Config) context.Context { return pipelinerunmetrics.WithClient(ctx) })
}
50 changes: 50 additions & 0 deletions pkg/pipelinerunmetrics/injection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright 2024 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pipelinerunmetrics

import (
"context"

"k8s.io/client-go/rest"
"knative.dev/pkg/injection"
"knative.dev/pkg/logging"
)

func init() {
injection.Default.RegisterClient(func(ctx context.Context, _ *rest.Config) context.Context { return WithClient(ctx) })
}

// RecorderKey is used for associating the Recorder inside the context.Context.
type RecorderKey struct{}

// WithClient adds a metrics recorder to the given context
func WithClient(ctx context.Context) context.Context {
rec, err := NewRecorder(ctx)
if err != nil {
logging.FromContext(ctx).Errorf("Failed to create pipelinerun metrics recorder %v", err)
}
return context.WithValue(ctx, RecorderKey{}, rec)
}

// Get extracts the pipelinerunmetrics.Recorder from the context.
func Get(ctx context.Context) *Recorder {
untyped := ctx.Value(RecorderKey{})
if untyped == nil {
logging.FromContext(ctx).Errorf("Unable to fetch *pipelinerunmetrics.Recorder from context.")
}
return untyped.(*Recorder)
}
144 changes: 144 additions & 0 deletions pkg/pipelinerunmetrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
Copyright 2024 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package pipelinerunmetrics

import (
"context"
"sync"

"github.com/tektoncd/chains/pkg/chains"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"knative.dev/pkg/logging"
"knative.dev/pkg/metrics"
)

var (
sgCount = stats.Float64(chains.PipelineRunSignedName,
chains.PipelineRunSignedDesc,
stats.UnitDimensionless)

sgCountView *view.View

plCount = stats.Float64(chains.PipelineRunUploadedName,
chains.PipelineRunUploadedDesc,
stats.UnitDimensionless)

plCountView *view.View

stCount = stats.Float64(chains.PipelineRunStoredName,
chains.PipelineRunStoredDesc,
stats.UnitDimensionless)

stCountView *view.View

mrCount = stats.Float64(chains.PipelineRunMarkedName,
chains.PipelineRunMarkedDesc,
stats.UnitDimensionless)

mrCountView *view.View
)

// Recorder holds keys for Tekton metrics
type Recorder struct {
initialized bool
}

// We cannot register the view multiple times, so NewRecorder lazily
// initializes this singleton and returns the same recorder across any
// subsequent invocations.
var (
once sync.Once
r *Recorder
)

// NewRecorder creates a new metrics recorder instance
// to log the PipelineRun related metrics
func NewRecorder(ctx context.Context) (*Recorder, error) {
var errRegistering error
logger := logging.FromContext(ctx)
once.Do(func() {
r = &Recorder{
initialized: true,
}
errRegistering = viewRegister()
if errRegistering != nil {
r.initialized = false
logger.Errorf("View Register Failed ", r.initialized)
return
}
})

return r, errRegistering
}

func viewRegister() error {
sgCountView = &view.View{
Description: sgCount.Description(),
Measure: sgCount,
Aggregation: view.Count(),
}

plCountView = &view.View{
Description: plCount.Description(),
Measure: plCount,
Aggregation: view.Count(),
}

stCountView = &view.View{
Description: stCount.Description(),
Measure: stCount,
Aggregation: view.Count(),
}

mrCountView = &view.View{
Description: mrCount.Description(),
Measure: mrCount,
Aggregation: view.Count(),
}
return view.Register(
sgCountView,
plCountView,
stCountView,
mrCountView,
)
}

func (r *Recorder) RecordCountMetrics(ctx context.Context, metricType string) {
logger := logging.FromContext(ctx)
if !r.initialized {
logger.Errorf("Ignoring the metrics recording as recorder not initialized ")
return
}
switch mt := metricType; mt {
case chains.SignedMessagesCount:
r.countMetrics(ctx, sgCount)
case chains.PayloadUploadeCount:
r.countMetrics(ctx, plCount)
case chains.SignsStoredCount:
r.countMetrics(ctx, stCount)
case chains.MarkedAsSignedCount:
r.countMetrics(ctx, mrCount)
default:
logger.Errorf("Ignoring the metrics recording as valid Metric type matching %v was not found", mt)
}

}

func (r *Recorder) countMetrics(ctx context.Context, measure *stats.Float64Measure) {
metrics.Record(ctx, measure.M(1))
}
Loading

0 comments on commit d5a5659

Please sign in to comment.