Add metering api.

Signed-off-by: yunkunrao <yunkunrao@yunify.com>
This commit is contained in:
yunkunrao
2020-10-16 11:01:50 +08:00
committed by Rao Yunkun
parent 2f5202f38a
commit e9073f0486
31 changed files with 4794 additions and 101 deletions

View File

@@ -25,4 +25,8 @@ type Interface interface {
GetNamedMetricsOverTime(metrics []string, start, end time.Time, step time.Duration, opt QueryOption) []Metric
GetMetadata(namespace string) []Metadata
GetMetricLabelSet(expr string, start, end time.Time) []map[string]string
// meter
GetNamedMeters(meters []string, time time.Time, opts []QueryOption) []Metric
GetNamedMetersOverTime(metrics []string, start, end time.Time, step time.Duration, opts []QueryOption) []Metric
}

View File

@@ -445,3 +445,11 @@ func (m metricsServer) GetMetricLabelSet(expr string, start, end time.Time) []ma
return res
}
// meter
func (m metricsServer) GetNamedMeters(meters []string, time time.Time, opts []monitoring.QueryOption) []monitoring.Metric {
return nil
}
func (m metricsServer) GetNamedMetersOverTime(metrics []string, start, end time.Time, step time.Duration, opts []monitoring.QueryOption) []monitoring.Metric {
return nil
}

View File

@@ -30,6 +30,8 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/monitoring"
)
const MeteringDefaultTimeout = 20 * time.Second
// prometheus implements monitoring interface backed by Prometheus
type prometheus struct {
client apiv1.API
@@ -147,6 +149,108 @@ func (p prometheus) GetNamedMetricsOverTime(metrics []string, start, end time.Ti
return res
}
func (p prometheus) GetNamedMeters(meters []string, ts time.Time, opts []monitoring.QueryOption) []monitoring.Metric {
var res []monitoring.Metric
var wg sync.WaitGroup
var mtx sync.Mutex
queryOptions := monitoring.NewQueryOptions()
for _, opt := range opts {
opt.Apply(queryOptions)
}
prometheusCtx, cancel := context.WithTimeout(context.Background(), MeteringDefaultTimeout)
defer cancel()
for _, meter := range meters {
wg.Add(1)
go func(metric string) {
parsedResp := monitoring.Metric{MetricName: metric}
begin := time.Now()
value, _, err := p.client.Query(prometheusCtx, makeMeterExpr(metric, *queryOptions), ts)
end := time.Now()
timeElapsed := end.Unix() - begin.Unix()
if timeElapsed > int64(MeteringDefaultTimeout.Seconds())/2 {
klog.Warningf("long time query[cost %v seconds], expr: %v", timeElapsed, makeMeterExpr(metric, *queryOptions))
}
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryResp(value, nil)
}
mtx.Lock()
res = append(res, parsedResp)
mtx.Unlock()
wg.Done()
}(meter)
}
wg.Wait()
return res
}
func (p prometheus) GetNamedMetersOverTime(meters []string, start, end time.Time, step time.Duration, opts []monitoring.QueryOption) []monitoring.Metric {
var res []monitoring.Metric
var wg sync.WaitGroup
var mtx sync.Mutex
queryOptions := monitoring.NewQueryOptions()
for _, opt := range opts {
opt.Apply(queryOptions)
}
timeRange := apiv1.Range{
Start: start,
End: end,
Step: step,
}
prometheusCtx, cancel := context.WithTimeout(context.Background(), MeteringDefaultTimeout)
defer cancel()
for _, meter := range meters {
wg.Add(1)
go func(metric string) {
parsedResp := monitoring.Metric{MetricName: metric}
begin := time.Now()
value, _, err := p.client.QueryRange(prometheusCtx, makeMeterExpr(metric, *queryOptions), timeRange)
end := time.Now()
timeElapsed := end.Unix() - begin.Unix()
if timeElapsed > int64(MeteringDefaultTimeout.Seconds())/2 {
klog.Warningf("long time query[cost %v seconds], expr: %v", timeElapsed, makeMeterExpr(metric, *queryOptions))
}
if err != nil {
parsedResp.Error = err.Error()
} else {
parsedResp.MetricData = parseQueryRangeResp(value, nil)
}
mtx.Lock()
res = append(res, parsedResp)
mtx.Unlock()
wg.Done()
}(meter)
}
wg.Wait()
return res
}
func (p prometheus) GetMetadata(namespace string) []monitoring.Metadata {
var meta []monitoring.Metadata
var matchTarget string

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,12 @@ limitations under the License.
package monitoring
import (
"fmt"
"strings"
"time"
)
type Level int
const (
@@ -23,17 +29,39 @@ const (
LevelNode
LevelWorkspace
LevelNamespace
LevelApplication
LevelWorkload
LevelService
LevelPod
LevelContainer
LevelPVC
LevelComponent
)
var MeteringLevelMap = map[string]int{
"LevelCluster": LevelCluster,
"LevelNode": LevelNode,
"LevelWorkspace": LevelWorkspace,
"LevelNamespace": LevelNamespace,
"LevelApplication": LevelApplication,
"LevelWorkload": LevelWorkload,
"LevelService": LevelService,
"LevelPod": LevelPod,
"LevelContainer": LevelContainer,
"LevelPVC": LevelPVC,
"LevelComponent": LevelComponent,
}
type QueryOption interface {
Apply(*QueryOptions)
}
type Meteroptions struct {
Start time.Time
End time.Time
Step time.Duration
}
type QueryOptions struct {
Level Level
@@ -48,6 +76,10 @@ type QueryOptions struct {
ContainerName string
StorageClassName string
PersistentVolumeClaimName string
PVCFilter string
ApplicationName string
ServiceName string
MeterOptions *Meteroptions
}
func NewQueryOptions() *QueryOptions {
@@ -61,31 +93,41 @@ func (_ ClusterOption) Apply(o *QueryOptions) {
}
type NodeOption struct {
ResourceFilter string
NodeName string
ResourceFilter string
NodeName string
PVCFilter string
StorageClassName string
}
func (no NodeOption) Apply(o *QueryOptions) {
o.Level = LevelNode
o.ResourceFilter = no.ResourceFilter
o.NodeName = no.NodeName
o.PVCFilter = no.PVCFilter
o.StorageClassName = no.StorageClassName
}
type WorkspaceOption struct {
ResourceFilter string
WorkspaceName string
ResourceFilter string
WorkspaceName string
PVCFilter string
StorageClassName string
}
func (wo WorkspaceOption) Apply(o *QueryOptions) {
o.Level = LevelWorkspace
o.ResourceFilter = wo.ResourceFilter
o.WorkspaceName = wo.WorkspaceName
o.PVCFilter = wo.PVCFilter
o.StorageClassName = wo.StorageClassName
}
type NamespaceOption struct {
ResourceFilter string
WorkspaceName string
NamespaceName string
ResourceFilter string
WorkspaceName string
NamespaceName string
PVCFilter string
StorageClassName string
}
func (no NamespaceOption) Apply(o *QueryOptions) {
@@ -93,6 +135,41 @@ func (no NamespaceOption) Apply(o *QueryOptions) {
o.ResourceFilter = no.ResourceFilter
o.WorkspaceName = no.WorkspaceName
o.NamespaceName = no.NamespaceName
o.PVCFilter = no.PVCFilter
o.StorageClassName = no.StorageClassName
}
type ApplicationsOption struct {
NamespaceName string
Applications []string
StorageClassName string
}
func (aso ApplicationsOption) Apply(o *QueryOptions) {
// nothing should be done
return
}
type ApplicationOption struct {
NamespaceName string
Application string
ApplicationComponents []string
StorageClassName string
}
func (ao ApplicationOption) Apply(o *QueryOptions) {
o.Level = LevelApplication
o.NamespaceName = ao.NamespaceName
o.ApplicationName = ao.Application
o.StorageClassName = ao.StorageClassName
app_components := strings.Join(ao.ApplicationComponents[:], "|")
if len(app_components) > 0 {
o.ResourceFilter = fmt.Sprintf(`namespace="%s", workload=~"%s"`, o.NamespaceName, app_components)
} else {
o.ResourceFilter = fmt.Sprintf(`namespace="%s", workload=~"%s"`, o.NamespaceName, ".*")
}
}
type WorkloadOption struct {
@@ -108,6 +185,37 @@ func (wo WorkloadOption) Apply(o *QueryOptions) {
o.WorkloadKind = wo.WorkloadKind
}
type ServicesOption struct {
NamespaceName string
Services []string
}
func (sso ServicesOption) Apply(o *QueryOptions) {
// nothing should be done
return
}
type ServiceOption struct {
ResourceFilter string
NamespaceName string
ServiceName string
PodNames []string
}
func (so ServiceOption) Apply(o *QueryOptions) {
o.Level = LevelService
o.NamespaceName = so.NamespaceName
o.ServiceName = so.ServiceName
pod_names := strings.Join(so.PodNames, "|")
if len(pod_names) > 0 {
o.ResourceFilter = fmt.Sprintf(`pod=~"%s", namespace="%s"`, pod_names, o.NamespaceName)
} else {
o.ResourceFilter = fmt.Sprintf(`pod=~"%s", namespace="%s"`, ".*", o.NamespaceName)
}
}
type PodOption struct {
NamespacedResourcesFilter string
ResourceFilter string
@@ -157,6 +265,9 @@ func (po PVCOption) Apply(o *QueryOptions) {
o.NamespaceName = po.NamespaceName
o.StorageClassName = po.StorageClassName
o.PersistentVolumeClaimName = po.PersistentVolumeClaimName
// for meter
o.PVCFilter = po.PersistentVolumeClaimName
}
type ComponentOption struct{}
@@ -164,3 +275,17 @@ type ComponentOption struct{}
func (_ ComponentOption) Apply(o *QueryOptions) {
o.Level = LevelComponent
}
type MeterOption struct {
Start time.Time
End time.Time
Step time.Duration
}
func (mo MeterOption) Apply(o *QueryOptions) {
o.MeterOptions = &Meteroptions{
Start: mo.Start,
End: mo.End,
Step: mo.Step,
}
}

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"github.com/json-iterator/go"
"strconv"
"time"
)
const (
@@ -54,8 +55,32 @@ type MetricValue struct {
// The type of Point is a float64 array with fixed length of 2.
// So Point will always be initialized as [0, 0], rather than nil.
// To allow empty Sample, we should declare Sample to type *Point
Sample *Point `json:"value,omitempty" description:"time series, values of vector type"`
Series []Point `json:"values,omitempty" description:"time series, values of matrix type"`
Sample *Point `json:"value,omitempty" description:"time series, values of vector type"`
Series []Point `json:"values,omitempty" description:"time series, values of matrix type"`
ExportSample *ExportPoint `json:"exported_value,omitempty" description:"exported time series, values of vector type"`
ExportedSeries []ExportPoint `json:"exported_values,omitempty" description:"exported time series, values of matrix type"`
MinValue float64 `json:"min_value" description:"minimum value from monitor points"`
MaxValue float64 `json:"max_value" description:"maximum value from monitor points"`
AvgValue float64 `json:"avg_value" description:"average value from monitor points"`
SumValue float64 `json:"sum_value" description:"sum value from monitor points"`
Fee float64 `json:"fee" description:"resource fee"`
}
func (mv *MetricValue) TransferToExportedMetricValue() {
if mv.Sample != nil {
sample := mv.Sample.transferToExported()
mv.ExportSample = &sample
mv.Sample = nil
}
for _, item := range mv.Series {
mv.ExportedSeries = append(mv.ExportedSeries, item.transferToExported())
}
mv.Series = nil
return
}
func (p Point) Timestamp() float64 {
@@ -66,6 +91,10 @@ func (p Point) Value() float64 {
return p[1]
}
func (p Point) transferToExported() ExportPoint {
return ExportPoint{p[0], p[1]}
}
// MarshalJSON implements json.Marshaler. It will be called when writing JSON to HTTP response
// Inspired by prometheus/client_golang
func (p Point) MarshalJSON() ([]byte, error) {
@@ -112,3 +141,27 @@ func (p *Point) UnmarshalJSON(b []byte) error {
p[1] = valf
return nil
}
type ExportPoint [2]float64
func (p ExportPoint) Timestamp() string {
return time.Unix(int64(p[0]), 0).Format("2006-01-02 03:04:05 PM")
}
func (p ExportPoint) Value() float64 {
return p[1]
}
// MarshalJSON implements json.Marshaler. It will be called when writing JSON to HTTP response
// Inspired by prometheus/client_golang
func (p ExportPoint) MarshalJSON() ([]byte, error) {
t, err := jsoniter.Marshal(p.Timestamp())
if err != nil {
return nil, err
}
v, err := jsoniter.Marshal(strconv.FormatFloat(p.Value(), 'f', -1, 64))
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
}