mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 22:29:01 +08:00
feat: add support for apdex settings (#3186)
This commit is contained in:
parent
b9409820cc
commit
cac637ac88
46
pkg/query-service/app/apdex.go
Normal file
46
pkg/query-service/app/apdex.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
req, err := parseSetApdexScoreRequest(r)
|
||||||
|
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dao.DB().SetApdexSettings(context.Background(), req); err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.WriteJSON(w, r, map[string]string{"data": "apdex score updated successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
services := r.URL.Query().Get("services")
|
||||||
|
apdexSet, err := dao.DB().GetApdexSettings(context.Background(), strings.Split(strings.TrimSpace(services), ","))
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.WriteJSON(w, r, apdexSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) getLatencyMetricMetadata(w http.ResponseWriter, r *http.Request) {
|
||||||
|
metricName := r.URL.Query().Get("metricName")
|
||||||
|
metricMetadata, err := aH.reader.GetLatencyMetricMetadata(r.Context(), metricName, aH.preferDelta)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aH.WriteJSON(w, r, metricMetadata)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -3829,6 +3830,52 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, req *v3
|
|||||||
return &attributeValues, nil
|
return &attributeValues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ClickHouseReader) GetLatencyMetricMetadata(ctx context.Context, metricName string, preferDelta bool) (*v3.LatencyMetricMetadataResponse, error) {
|
||||||
|
query := fmt.Sprintf("SELECT DISTINCT(temporality) from %s.%s WHERE metric_name='%s'", signozMetricDBName, signozTSTableName, metricName)
|
||||||
|
rows, err := r.db.Query(ctx, query, metricName)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Error(err)
|
||||||
|
return nil, fmt.Errorf("error while executing query: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var deltaExists bool
|
||||||
|
for rows.Next() {
|
||||||
|
var temporality string
|
||||||
|
if err := rows.Scan(&temporality); err != nil {
|
||||||
|
return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
|
||||||
|
}
|
||||||
|
if temporality == string(v3.Delta) {
|
||||||
|
deltaExists = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query = fmt.Sprintf("SELECT DISTINCT(toFloat64(JSONExtractString(labels, 'le'))) as le from %s.%s WHERE metric_name='%s' ORDER BY le", signozMetricDBName, signozTSTableName, metricName)
|
||||||
|
rows, err = r.db.Query(ctx, query, metricName)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Error(err)
|
||||||
|
return nil, fmt.Errorf("error while executing query: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var leFloat64 []float64
|
||||||
|
for rows.Next() {
|
||||||
|
var le float64
|
||||||
|
if err := rows.Scan(&le); err != nil {
|
||||||
|
return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
|
||||||
|
}
|
||||||
|
if math.IsInf(le, 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
leFloat64 = append(leFloat64, le)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v3.LatencyMetricMetadataResponse{
|
||||||
|
Delta: deltaExists && preferDelta,
|
||||||
|
Le: leFloat64,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isColumn(tableStatement, field string) bool {
|
func isColumn(tableStatement, field string) bool {
|
||||||
return strings.Contains(tableStatement, fmt.Sprintf("`%s` ", field))
|
return strings.Contains(tableStatement, fmt.Sprintf("`%s` ", field))
|
||||||
}
|
}
|
||||||
|
@ -332,6 +332,10 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
|
|||||||
router.HandleFunc("/api/v1/dependency_graph", am.ViewAccess(aH.dependencyGraph)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/dependency_graph", am.ViewAccess(aH.dependencyGraph)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/settings/ttl", am.AdminAccess(aH.setTTL)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/settings/ttl", am.AdminAccess(aH.setTTL)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/settings/ttl", am.ViewAccess(aH.getTTL)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/settings/ttl", am.ViewAccess(aH.getTTL)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v1/settings/apdex", am.AdminAccess(aH.setApdexSettings)).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc("/api/v1/settings/apdex", am.ViewAccess(aH.getApdexSettings)).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v1/metric_meta", am.ViewAccess(aH.getLatencyMetricMetadata)).Methods(http.MethodGet)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
||||||
|
@ -726,6 +726,14 @@ func parseInviteRequest(r *http.Request) (*model.InviteRequest, error) {
|
|||||||
return &req, nil
|
return &req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSetApdexScoreRequest(r *http.Request) (*model.ApdexSettings, error) {
|
||||||
|
var req model.ApdexSettings
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &req, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) {
|
func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) {
|
||||||
var req auth.RegisterRequest
|
var req auth.RegisterRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
@ -32,6 +32,8 @@ type Queries interface {
|
|||||||
GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError)
|
GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError)
|
||||||
GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError)
|
GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError)
|
||||||
GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError)
|
GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError)
|
||||||
|
|
||||||
|
GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutations interface {
|
type Mutations interface {
|
||||||
@ -56,4 +58,6 @@ type Mutations interface {
|
|||||||
|
|
||||||
UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError
|
UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError
|
||||||
UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError
|
UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError
|
||||||
|
|
||||||
|
SetApdexSettings(ctx context.Context, set *model.ApdexSettings) *model.ApiError
|
||||||
}
|
}
|
||||||
|
74
pkg/query-service/dao/sqlite/apdex.go
Normal file
74
pkg/query-service/dao/sqlite/apdex.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultApdexThreshold = 0.5
|
||||||
|
|
||||||
|
func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError) {
|
||||||
|
var apdexSettings []model.ApdexSettings
|
||||||
|
var serviceName string
|
||||||
|
|
||||||
|
for i, service := range services {
|
||||||
|
if i == 0 {
|
||||||
|
serviceName = fmt.Sprintf("'%s'", service)
|
||||||
|
} else {
|
||||||
|
serviceName = fmt.Sprintf("%s, '%s'", serviceName, service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("SELECT * FROM apdex_settings WHERE service_name IN (%s)", serviceName)
|
||||||
|
|
||||||
|
err := mds.db.Select(&apdexSettings, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &model.ApiError{
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add default apdex settings for services that don't have any
|
||||||
|
for _, service := range services {
|
||||||
|
var found bool
|
||||||
|
for _, apdexSetting := range apdexSettings {
|
||||||
|
if apdexSetting.ServiceName == service {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
apdexSettings = append(apdexSettings, model.ApdexSettings{
|
||||||
|
ServiceName: service,
|
||||||
|
Threshold: defaultApdexThreshold,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apdexSettings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, apdexSettings *model.ApdexSettings) *model.ApiError {
|
||||||
|
|
||||||
|
fmt.Println("apdexSettings:", apdexSettings)
|
||||||
|
_, err := mds.db.NamedExec(`
|
||||||
|
INSERT OR REPLACE INTO apdex_settings (
|
||||||
|
service_name,
|
||||||
|
threshold,
|
||||||
|
exclude_status_codes
|
||||||
|
) VALUES (
|
||||||
|
:service_name,
|
||||||
|
:threshold,
|
||||||
|
:exclude_status_codes
|
||||||
|
)`, apdexSettings)
|
||||||
|
if err != nil {
|
||||||
|
return &model.ApiError{
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -73,6 +73,11 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) {
|
|||||||
flags TEXT,
|
flags TEXT,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS apdex_settings (
|
||||||
|
service_name TEXT PRIMARY KEY,
|
||||||
|
threshold FLOAT NOT NULL,
|
||||||
|
exclude_status_codes TEXT NOT NULL
|
||||||
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = db.Exec(table_schema)
|
_, err = db.Exec(table_schema)
|
||||||
|
@ -95,6 +95,8 @@ type Reader interface {
|
|||||||
|
|
||||||
QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error)
|
QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error)
|
||||||
CheckClickHouse(ctx context.Context) error
|
CheckClickHouse(ctx context.Context) error
|
||||||
|
|
||||||
|
GetLatencyMetricMetadata(context.Context, string, bool) (*v3.LatencyMetricMetadataResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
|
@ -36,6 +36,12 @@ type User struct {
|
|||||||
GroupId string `json:"groupId,omitempty" db:"group_id"`
|
GroupId string `json:"groupId,omitempty" db:"group_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApdexSettings struct {
|
||||||
|
ServiceName string `json:"serviceName" db:"service_name"`
|
||||||
|
Threshold float64 `json:"threshold" db:"threshold"`
|
||||||
|
ExcludeStatusCodes string `json:"excludeStatusCodes" db:"exclude_status_codes"` // sqlite doesn't support array type
|
||||||
|
}
|
||||||
|
|
||||||
type UserFlag map[string]string
|
type UserFlag map[string]string
|
||||||
|
|
||||||
func (uf UserFlag) Value() (driver.Value, error) {
|
func (uf UserFlag) Value() (driver.Value, error) {
|
||||||
|
@ -663,3 +663,8 @@ func (eq *ExplorerQuery) Validate() error {
|
|||||||
}
|
}
|
||||||
return eq.CompositeQuery.Validate()
|
return eq.CompositeQuery.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LatencyMetricMetadataResponse struct {
|
||||||
|
Delta bool `json:"delta"`
|
||||||
|
Le []float64 `json:"le"`
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user