Skip to content

Commit

Permalink
Merge pull request #2 from SOF3/add-generator-monitor
Browse files Browse the repository at this point in the history
feat(generator): add global podprotector monitor metrics
  • Loading branch information
SOF3 authored Nov 24, 2024
2 parents 31a0c7a + 579ab44 commit 9c15fbf
Show file tree
Hide file tree
Showing 15 changed files with 705 additions and 20 deletions.
6 changes: 4 additions & 2 deletions allinone/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ import (
"github.com/kubewharf/podseidon/aggregator/aggregator"
aggregatorobserver "github.com/kubewharf/podseidon/aggregator/observer"
"github.com/kubewharf/podseidon/generator/generator"
"github.com/kubewharf/podseidon/generator/monitor"
generatorobserver "github.com/kubewharf/podseidon/generator/observer"
"github.com/kubewharf/podseidon/generator/resource"
"github.com/kubewharf/podseidon/generator/resource/deployment"
webhookobserver "github.com/kubewharf/podseidon/webhook/observer"
"github.com/kubewharf/podseidon/webhook/server"
webhookserver "github.com/kubewharf/podseidon/webhook/server"
)

func main() {
Expand All @@ -54,6 +55,7 @@ func main() {
},
},
)),
component.RequireDep(server.New(util.Empty{})),
component.RequireDep(monitor.New(monitor.Args{})),
component.RequireDep(webhookserver.New(util.Empty{})),
)
}
2 changes: 2 additions & 0 deletions chart/templates/_generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ generator-worker-concurrency: {{toJson .main.Values.generator.workerCount}}
{{- if empty $selector | not}}
deployment-plugin-protection-selector: {{toJson $selector}}
{{- end}}

generator-monitor-enable: {{toJson .main.Values.generator.monitor.enable}}
{{- end}}

{{- define "podseidon.generator.env.yaml"}}
Expand Down
3 changes: 3 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ generator:
protectedSelector: # Only objects matching the selector have a generated protector.
deployments.apps: 'podseidon.kubewharf.io/protect=true'

monitor: # Report global PodProtector metrics
enable: true

aggregator:
replicas: 3
minReadySeconds: 60
Expand Down
2 changes: 2 additions & 0 deletions generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
workerobserver "github.com/kubewharf/podseidon/util/worker/observer"

"github.com/kubewharf/podseidon/generator/generator"
"github.com/kubewharf/podseidon/generator/monitor"
generatorobserver "github.com/kubewharf/podseidon/generator/observer"
"github.com/kubewharf/podseidon/generator/resource"
"github.com/kubewharf/podseidon/generator/resource/deployment"
Expand All @@ -47,5 +48,6 @@ func main() {
},
},
)),
component.RequireDep(monitor.New(monitor.Args{})),
)
}
162 changes: 162 additions & 0 deletions generator/monitor/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright 2024 The Podseidon 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.

// Reports global metrics about all PodProtector objects.
package monitor

import (
"context"
"flag"
"sync"

"k8s.io/apimachinery/pkg/types"

podseidonv1a1 "github.com/kubewharf/podseidon/apis/v1alpha1"
podseidoninformers "github.com/kubewharf/podseidon/client/informers/externalversions"

"github.com/kubewharf/podseidon/util/component"
"github.com/kubewharf/podseidon/util/errors"
"github.com/kubewharf/podseidon/util/kube"
"github.com/kubewharf/podseidon/util/o11y"
"github.com/kubewharf/podseidon/util/optional"
"github.com/kubewharf/podseidon/util/util"

"github.com/kubewharf/podseidon/generator/constants"
"github.com/kubewharf/podseidon/generator/observer"
)

const ProportionPpmUnits = 1000000

var New = component.Declare[Args, Options, Deps, State, util.Empty](
func(Args) string { return "generator-monitor" },
func(_ Args, fs *flag.FlagSet) Options {
return Options{
Enable: fs.Bool("enable", true, "Enable global PodProtector monitor"),
}
},
func(_ Args, requests *component.DepRequests) Deps {
return Deps{
observer: o11y.Request[observer.Observer](requests),
podseidonInformers: component.DepPtr(requests, kube.NewInformers(kube.PodseidonInformers(
constants.CoreClusterName,
constants.LeaderPhase,
optional.Some(constants.GeneratorElectorArgs),
))),
}
},
func(_ context.Context, _ Args, options Options, deps Deps) (*State, error) {
state := &State{
statusMu: sync.Mutex{},
status: util.Zero[observer.MonitorWorkloads](),
addedDeltaCache: map[types.NamespacedName]observer.MonitorWorkloads{},
}

if *options.Enable {
pprInformer := deps.podseidonInformers.Get().Podseidon().V1alpha1().PodProtectors()
_, err := pprInformer.Informer().AddEventHandler(kube.GenericEventHandlerWithStaleState(
func(ppr *podseidonv1a1.PodProtector, stillPresent bool) {
nsName := types.NamespacedName{Namespace: ppr.Namespace, Name: ppr.Name}

if stillPresent {
status := pprToStatus(ppr)
state.add(nsName, status)
} else {
state.remove(nsName)
}
},
))
if err != nil {
return nil, errors.TagWrapf("AddEventHandler", err, "add event handler to ppr informer")
}
}

return state, nil
},
component.Lifecycle[Args, Options, Deps, State]{
Start: func(ctx context.Context, _ *Args, options *Options, deps *Deps, state *State) error {
if *options.Enable {
deps.observer.Get().MonitorWorkloads(ctx, util.Empty{}, func() observer.MonitorWorkloads {
state.statusMu.Lock()
defer state.statusMu.Unlock()

return state.status
})
}

return nil
},
Join: nil,
HealthChecks: nil,
},
func(_ *component.Data[Args, Options, Deps, State]) util.Empty { return util.Empty{} },
)

type Args struct{}

type Options struct {
Enable *bool
}

type Deps struct {
observer component.Dep[observer.Observer]
podseidonInformers component.Dep[podseidoninformers.SharedInformerFactory]
}

type State struct {
statusMu sync.Mutex
status observer.MonitorWorkloads

addedDeltaCache map[types.NamespacedName]observer.MonitorWorkloads
}

func pprToStatus(ppr *podseidonv1a1.PodProtector) observer.MonitorWorkloads {
availableProportionPpm := int64(0)
if ppr.Spec.MinAvailable > 0 {
availableProportionPpm = min(ProportionPpmUnits, util.RoundedIntDiv(
int64(ppr.Status.Summary.AggregatedAvailable)*ProportionPpmUnits,
int64(ppr.Spec.MinAvailable),
))
}

return observer.MonitorWorkloads{
NumWorkloads: 1,
MinAvailable: int64(ppr.Spec.MinAvailable),
TotalReplicas: int64(ppr.Status.Summary.Total),
AggregatedAvailableReplicas: int64(ppr.Status.Summary.AggregatedAvailable),
EstimatedAvailableReplicas: int64(ppr.Status.Summary.EstimatedAvailable),
SumAvailableProportionPpm: availableProportionPpm,
SumLatencyMillis: ppr.Status.Summary.MaxLatencyMillis,
}
}

func (state *State) add(nsName types.NamespacedName, newDelta observer.MonitorWorkloads) {
old := state.addedDeltaCache[nsName] // use zero value if absent
state.addedDeltaCache[nsName] = newDelta

state.statusMu.Lock()
defer state.statusMu.Unlock()

state.status.Subtract(old)
state.status.Add(newDelta)
}

func (state *State) remove(nsName types.NamespacedName) {
old := state.addedDeltaCache[nsName]
delete(state.addedDeltaCache, nsName)

state.statusMu.Lock()
defer state.statusMu.Unlock()

state.status.Subtract(old)
}
Loading

0 comments on commit 9c15fbf

Please sign in to comment.