mirror of
https://github.com/harness/drone.git
synced 2025-05-08 17:39:58 +08:00
236 lines
6.3 KiB
Go
236 lines
6.3 KiB
Go
// Copyright 2023 Harness, Inc.
|
|
//
|
|
// 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 database
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/harness/gitness/app/store"
|
|
gitness_store "github.com/harness/gitness/store"
|
|
"github.com/harness/gitness/store/database"
|
|
"github.com/harness/gitness/store/database/dbtx"
|
|
"github.com/harness/gitness/types"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
var _ store.UsageMetricStore = (*UsageMetricsStore)(nil)
|
|
|
|
// NewUsageMetricsStore returns a new UsageMetricsStore.
|
|
func NewUsageMetricsStore(db *sqlx.DB) *UsageMetricsStore {
|
|
return &UsageMetricsStore{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// UsageMetricsStore implements store.UsageMetrics backed by a relational database.
|
|
type UsageMetricsStore struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
func (s *UsageMetricsStore) getVersion(
|
|
ctx context.Context,
|
|
rootSpaceID int64,
|
|
date int64,
|
|
) int64 {
|
|
const sqlQuery = `
|
|
SELECT
|
|
usage_metric_version
|
|
FROM usage_metrics
|
|
WHERE usage_metric_space_id = $1 AND usage_metric_date = $2
|
|
`
|
|
var version int64
|
|
err := s.db.QueryRowContext(ctx, sqlQuery, rootSpaceID, date).Scan(&version)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return version
|
|
}
|
|
|
|
func (s *UsageMetricsStore) Upsert(ctx context.Context, in *types.UsageMetric) error {
|
|
const sqlQuery = `
|
|
INSERT INTO usage_metrics (
|
|
usage_metric_space_id
|
|
,usage_metric_date
|
|
,usage_metric_created
|
|
,usage_metric_updated
|
|
,usage_metric_bandwidth
|
|
,usage_metric_storage
|
|
,usage_metric_version
|
|
) VALUES (
|
|
:usage_metric_space_id
|
|
,:usage_metric_date
|
|
,:usage_metric_created
|
|
,:usage_metric_updated
|
|
,:usage_metric_bandwidth
|
|
,:usage_metric_storage
|
|
,:usage_metric_version
|
|
)
|
|
ON CONFLICT (usage_metric_space_id, usage_metric_date)
|
|
DO UPDATE
|
|
SET
|
|
usage_metric_version = EXCLUDED.usage_metric_version
|
|
,usage_metric_updated = EXCLUDED.usage_metric_updated
|
|
,usage_metric_bandwidth = usage_metrics.usage_metric_bandwidth + EXCLUDED.usage_metric_bandwidth
|
|
,usage_metric_storage = usage_metrics.usage_metric_storage + EXCLUDED.usage_metric_storage
|
|
WHERE usage_metrics.usage_metric_version = EXCLUDED.usage_metric_version - 1`
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
today := s.Date(time.Now())
|
|
query, args, err := db.BindNamed(sqlQuery, usageMetric{
|
|
RootSpaceID: in.RootSpaceID,
|
|
Date: today,
|
|
Created: time.Now().UnixMilli(),
|
|
Updated: time.Now().UnixMilli(),
|
|
Bandwidth: in.Bandwidth,
|
|
Storage: in.Storage,
|
|
Version: s.getVersion(ctx, in.RootSpaceID, today) + 1,
|
|
})
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "failed to bind query")
|
|
}
|
|
|
|
result, err := db.ExecContext(ctx, query, args...)
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "failed to upsert usage_metric")
|
|
}
|
|
n, err := result.RowsAffected()
|
|
if err != nil {
|
|
return database.ProcessSQLErrorf(ctx, err, "failed to fetch number of rows affected")
|
|
}
|
|
if n == 0 {
|
|
return gitness_store.ErrVersionConflict
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpsertOptimistic upsert the usage metric details using the optimistic locking mechanism.
|
|
func (s *UsageMetricsStore) UpsertOptimistic(
|
|
ctx context.Context,
|
|
in *types.UsageMetric,
|
|
) error {
|
|
for {
|
|
err := s.Upsert(ctx, in)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !errors.Is(err, gitness_store.ErrVersionConflict) {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *UsageMetricsStore) GetMetrics(
|
|
ctx context.Context,
|
|
rootSpaceID int64,
|
|
start int64,
|
|
end int64,
|
|
) (*types.UsageMetric, error) {
|
|
const sqlQuery = `
|
|
SELECT
|
|
COALESCE(SUM(usage_metric_bandwidth), 0) AS usage_metric_bandwidth,
|
|
COALESCE(SUM(usage_metric_storage), 0) AS usage_metric_storage
|
|
FROM usage_metrics
|
|
WHERE
|
|
usage_metric_space_id = $1 AND
|
|
usage_metric_date BETWEEN $2 AND $3`
|
|
|
|
result := &types.UsageMetric{
|
|
RootSpaceID: rootSpaceID,
|
|
}
|
|
|
|
startTime := time.UnixMilli(start)
|
|
endTime := time.UnixMilli(end)
|
|
|
|
err := s.db.QueryRowContext(
|
|
ctx,
|
|
sqlQuery,
|
|
rootSpaceID,
|
|
s.Date(startTime),
|
|
s.Date(endTime),
|
|
).Scan(
|
|
&result.Bandwidth,
|
|
&result.Storage,
|
|
)
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "failed to get metric")
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *UsageMetricsStore) List(
|
|
ctx context.Context,
|
|
start int64,
|
|
end int64,
|
|
) ([]types.UsageMetric, error) {
|
|
const sqlQuery = `
|
|
SELECT
|
|
usage_metric_space_id,
|
|
COALESCE(SUM(usage_metric_bandwidth), 0) AS usage_metric_bandwidth,
|
|
COALESCE(SUM(usage_metric_storage), 0) AS usage_metric_storage
|
|
FROM usage_metrics
|
|
WHERE
|
|
usage_metric_date BETWEEN $1 AND $2
|
|
GROUP BY usage_metric_space_id
|
|
ORDER BY usage_metric_bandwidth DESC, usage_metric_storage DESC`
|
|
|
|
startTime := time.UnixMilli(start)
|
|
endTime := time.UnixMilli(end)
|
|
|
|
db := dbtx.GetAccessor(ctx, s.db)
|
|
rows, err := db.QueryContext(ctx, sqlQuery, s.Date(startTime), s.Date(endTime))
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "failed to list usage_metrics")
|
|
}
|
|
defer rows.Close()
|
|
|
|
results := make([]types.UsageMetric, 0, 16)
|
|
for rows.Next() {
|
|
metric := types.UsageMetric{}
|
|
err = rows.Scan(
|
|
&metric.RootSpaceID,
|
|
&metric.Bandwidth,
|
|
&metric.Storage,
|
|
)
|
|
if err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "failed to scan usage_metrics")
|
|
}
|
|
results = append(results, metric)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
return nil, database.ProcessSQLErrorf(ctx, err, "failed to list usage_metrics")
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *UsageMetricsStore) Date(t time.Time) int64 {
|
|
year, month, day := t.Date()
|
|
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC).UnixMilli()
|
|
}
|
|
|
|
type usageMetric struct {
|
|
RootSpaceID int64 `db:"usage_metric_space_id"`
|
|
Date int64 `db:"usage_metric_date"`
|
|
Created int64 `db:"usage_metric_created"`
|
|
Updated int64 `db:"usage_metric_updated"`
|
|
Bandwidth int64 `db:"usage_metric_bandwidth"`
|
|
Storage int64 `db:"usage_metric_storage"`
|
|
Version int64 `db:"usage_metric_version"`
|
|
}
|