Compare commits

..

2 Commits

Author SHA1 Message Date
Vinícius Lourenço
8b23d7104b feat(typography): migrate to @signozhq/ui 2026-05-05 22:08:24 -03:00
Nityananda Gohain
a7fde606ca fix: use ms in prepareFillZeroArgsWithStep (#11196)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
2026-05-05 18:27:19 +00:00
395 changed files with 1105 additions and 4896 deletions

View File

@@ -18,19 +18,16 @@ import (
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/gateway/noopgateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
"github.com/SigNoz/signoz/pkg/meterreporter"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
@@ -112,9 +109,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ licensing.Licensing) factory.NamedMap[factory.ProviderFactory[auditor.Auditor, auditor.Config]] {
return signoz.NewAuditorProviderFactories()
},
func(_ context.Context, _ flagger.Flagger, _ licensing.Licensing, _ telemetrystore.TelemetryStore, _ retention.Getter, _ organization.Getter, _ zeus.Zeus) (factory.NamedMap[factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config]], string) {
return signoz.NewMeterReporterProviderFactories(), "noop"
},
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
return querier.NewHandler(ps, q, a)
},

View File

@@ -17,14 +17,6 @@ import (
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/metercollector/baseplatformfeemetercollector"
"github.com/SigNoz/signoz/ee/metercollector/datapointcountmetercollector"
"github.com/SigNoz/signoz/ee/metercollector/datapointsizemetercollector"
"github.com/SigNoz/signoz/ee/metercollector/logcountmetercollector"
"github.com/SigNoz/signoz/ee/metercollector/logsizemetercollector"
"github.com/SigNoz/signoz/ee/metercollector/spancountmetercollector"
"github.com/SigNoz/signoz/ee/metercollector/spansizemetercollector"
"github.com/SigNoz/signoz/ee/meterreporter/httpmeterreporter"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration/implcloudprovider"
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
@@ -43,18 +35,14 @@ import (
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
pkgflagger "github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/meterreporter"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
@@ -69,10 +57,7 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
)
@@ -172,19 +157,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
}
return factories
},
func(ctx context.Context, flagger pkgflagger.Flagger, licensing licensing.Licensing, telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter, orgGetter organization.Getter, zeus zeus.Zeus) (factory.NamedMap[factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config]], string) {
factories := signoz.NewMeterReporterProviderFactories()
if err := factories.Add(httpmeterreporter.NewFactory(newMeterCollectors(licensing, telemetryStore, retentionGetter), licensing, telemetryStore, orgGetter, zeus)); err != nil {
panic(err)
}
evalCtx := featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})
if flagger.BooleanOrEmpty(ctx, pkgflagger.FeatureUseMeterReporter, evalCtx) {
return factories, "http"
}
return factories, "noop"
},
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
communityHandler := querier.NewHandler(ps, q, a)
return eequerier.NewHandler(ps, q, communityHandler)
@@ -244,15 +216,3 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil
}
func newMeterCollectors(licensing licensing.Licensing, telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) map[zeustypes.MeterName]metercollector.MeterCollector {
return map[zeustypes.MeterName]metercollector.MeterCollector{
baseplatformfeemetercollector.MeterName: baseplatformfeemetercollector.New(licensing),
logcountmetercollector.MeterName: logcountmetercollector.New(telemetryStore, retentionGetter),
logsizemetercollector.MeterName: logsizemetercollector.New(telemetryStore, retentionGetter),
datapointcountmetercollector.MeterName: datapointcountmetercollector.New(telemetryStore, retentionGetter),
datapointsizemetercollector.MeterName: datapointsizemetercollector.New(telemetryStore, retentionGetter),
spancountmetercollector.MeterName: spancountmetercollector.New(telemetryStore, retentionGetter),
spansizemetercollector.MeterName: spansizemetercollector.New(telemetryStore, retentionGetter),
}
}

View File

@@ -429,10 +429,3 @@ authz:
openfga:
# maximum tuples allowed per openfga write operation.
max_tuples_per_write: 300
##################### Meter Reporter #####################
meterreporter:
# The interval between collection ticks. Minimum 5m.
interval: 6h
# The per-tick timeout that bounds collect-and-ship work. Minimum 3m and must be less than interval.
timeout: 5m

View File

@@ -1,6 +1,6 @@
module base
type organization
type organization
relations
define read: [user, serviceaccount, role#assignee]
define update: [user, serviceaccount, role#assignee]
@@ -46,4 +46,4 @@ type metaresource
type telemetryresource
relations
define read: [user, serviceaccount, role#assignee]
define read: [user, serviceaccount, role#assignee]

View File

@@ -1,53 +0,0 @@
// Package baseplatformfeemetercollector collects the license-derived base platform fee meter.
package baseplatformfeemetercollector
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.base.platform.fee")
meterUnit = zeustypes.MeterUnitCount
meterAggregation = zeustypes.MeterAggregationMax
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects base platform fee meters.
type Provider struct {
licensing licensing.Licensing
}
func New(licensing licensing.Licensing) *Provider {
return &Provider{licensing: licensing}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect emits value 1 when the org has an active license.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
license, err := p.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "fetch active license for base platform fee meter")
}
if license == nil || license.Key == "" {
return nil, nil
}
return []zeustypes.Meter{
zeustypes.NewMeter(MeterName, 1, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
}),
}, nil
}

View File

@@ -1,95 +0,0 @@
package baseplatformfeemetercollector
import (
"context"
"testing"
"time"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestCollectEmitsBasePlatformFeeMeterForValidLicense(t *testing.T) {
orgID := valuer.GenerateUUID()
window := completedWindow()
provider := New(&fakeLicensing{
license: &licensetypes.License{Key: "license-key"},
})
readings, err := provider.Collect(context.Background(), orgID, window)
require.NoError(t, err)
require.Equal(t, []zeustypes.Meter{
zeustypes.NewMeter(MeterName, 1, zeustypes.MeterUnitCount, zeustypes.MeterAggregationMax, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
}),
}, readings)
}
func TestCollectSkipsNilLicense(t *testing.T) {
readings, err := New(&fakeLicensing{}).Collect(context.Background(), valuer.GenerateUUID(), completedWindow())
require.NoError(t, err)
require.Empty(t, readings)
}
func TestProviderMetadata(t *testing.T) {
provider := New(&fakeLicensing{})
require.Equal(t, "signoz.meter.base.platform.fee", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitCount, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationMax, provider.Aggregation())
}
func completedWindow() zeustypes.MeterWindow {
start := time.Date(2026, 5, 4, 0, 0, 0, 0, time.UTC)
return zeustypes.MustNewMeterWindow(start.UnixMilli(), start.AddDate(0, 0, 1).UnixMilli(), true)
}
var _ licensing.Licensing = (*fakeLicensing)(nil)
type fakeLicensing struct {
license *licensetypes.License
err error
}
func (f *fakeLicensing) Start(context.Context) error {
return nil
}
func (f *fakeLicensing) Stop(context.Context) error {
return nil
}
func (f *fakeLicensing) Validate(context.Context) error {
return nil
}
func (f *fakeLicensing) Activate(context.Context, valuer.UUID, string) error {
return nil
}
func (f *fakeLicensing) GetActive(context.Context, valuer.UUID) (*licensetypes.License, error) {
return f.license, f.err
}
func (f *fakeLicensing) Refresh(context.Context, valuer.UUID) error {
return nil
}
func (f *fakeLicensing) Checkout(context.Context, valuer.UUID, *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
return &licensetypes.GettableSubscription{}, nil
}
func (f *fakeLicensing) Portal(context.Context, valuer.UUID, *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
return &licensetypes.GettableSubscription{}, nil
}
func (f *fakeLicensing) GetFeatureFlags(context.Context, valuer.UUID) ([]*licensetypes.Feature, error) {
return nil, nil
}
func (f *fakeLicensing) Collect(context.Context, valuer.UUID) (map[string]any, error) {
return map[string]any{}, nil
}

View File

@@ -1,271 +0,0 @@
// Package datapointcountmetercollector collects metric datapoint count meters
// by workspace and retention. Keep the query local to this meter.
package datapointcountmetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.metric.datapoint.count")
meterUnit = zeustypes.MeterUnitCount
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects datapoint count meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates datapoint count for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrymetrics.DBName,
telemetrymetrics.SamplesV4LocalTableName,
retentiontypes.DefaultMetricsRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package datapointcountmetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.metric.datapoint.count", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitCount, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,271 +0,0 @@
// Package datapointsizemetercollector collects metric datapoint size meters
// by workspace and retention. Keep the query local to this meter.
package datapointsizemetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrymetrics"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.metric.datapoint.size")
meterUnit = zeustypes.MeterUnitBytes
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects datapoint size meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates datapoint size for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrymetrics.DBName,
telemetrymetrics.SamplesV4LocalTableName,
retentiontypes.DefaultMetricsRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package datapointsizemetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.metric.datapoint.size", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitBytes, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,271 +0,0 @@
// Package logcountmetercollector collects log count meters by workspace and
// retention. Keep the query local to this meter.
package logcountmetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrylogs"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.log.count")
meterUnit = zeustypes.MeterUnitCount
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects log count meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates log count for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrylogs.DBName,
telemetrylogs.LogsV2LocalTableName,
retentiontypes.DefaultLogsRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package logcountmetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.log.count", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitCount, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,271 +0,0 @@
// Package logsizemetercollector collects log size meters by workspace and
// retention. Keep the query local to this meter.
package logsizemetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrylogs"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.log.size")
meterUnit = zeustypes.MeterUnitBytes
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects log size meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates log size for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrylogs.DBName,
telemetrylogs.LogsV2LocalTableName,
retentiontypes.DefaultLogsRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package logsizemetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.log.size", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitBytes, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,271 +0,0 @@
// Package spancountmetercollector collects span count meters by workspace and
// retention. Keep the query local to this meter.
package spancountmetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.span.count")
meterUnit = zeustypes.MeterUnitCount
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects span count meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates span count for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrytraces.DBName,
telemetrytraces.SpanIndexV3LocalTableName,
retentiontypes.DefaultTracesRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package spancountmetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.span.count", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitCount, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,271 +0,0 @@
// Package spansizemetercollector collects span size meters by workspace and
// retention. Keep the query local to this meter.
package spansizemetercollector
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"github.com/huandu/go-sqlbuilder"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/modules/retention"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
// MeterName is the typed registry key for this collector.
var (
MeterName = zeustypes.MustNewMeterName("signoz.meter.span.size")
meterUnit = zeustypes.MeterUnitBytes
meterAggregation = zeustypes.MeterAggregationSum
)
var _ metercollector.MeterCollector = (*Provider)(nil)
// Provider collects span size meters.
type Provider struct {
telemetryStore telemetrystore.TelemetryStore
retentionGetter retention.Getter
}
func New(telemetryStore telemetrystore.TelemetryStore, retentionGetter retention.Getter) *Provider {
return &Provider{
telemetryStore: telemetryStore,
retentionGetter: retentionGetter,
}
}
func (p *Provider) Name() zeustypes.MeterName { return MeterName }
func (p *Provider) Unit() zeustypes.MeterUnit { return meterUnit }
func (p *Provider) Aggregation() zeustypes.MeterAggregation {
return meterAggregation
}
// Collect aggregates span size for the window and emits an empty-day sentinel.
func (p *Provider) Collect(ctx context.Context, orgID valuer.UUID, window zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
meterName := MeterName.String()
slices, err := p.retentionGetter.ActiveSlices(
ctx,
orgID,
telemetrytraces.DBName,
telemetrytraces.SpanIndexV3LocalTableName,
retentiontypes.DefaultTracesRetentionDays,
window.StartUnixMilli,
window.EndUnixMilli,
)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "load retention slices for meter %q", meterName)
}
type bucket struct {
dimensions map[string]string
value float64
}
accumulator := make(map[string]*bucket)
for _, slice := range slices {
query, args, dimensionColumns, err := buildQuery(meterName, slice, p.retentionGetter)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build retention query for meter %q", meterName)
}
rows, err := p.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "query meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
if err := func() error {
defer rows.Close()
for rows.Next() {
dimensionValues := make([]string, len(dimensionColumns))
var retentionDays int32
var retentionRuleIndex int32
var value float64
scanDest := make([]any, 0, len(dimensionValues)+3)
for i := range dimensionValues {
scanDest = append(scanDest, &dimensionValues[i])
}
scanDest = append(scanDest, &retentionDays, &retentionRuleIndex, &value)
if err := rows.Scan(scanDest...); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "scan meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
dimensions, err := buildDimensions(orgID, int(retentionDays), int(retentionRuleIndex), dimensionColumns, dimensionValues, slice.Rules)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "build dimensions for meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
key := bucketKey(dimensions)
b, ok := accumulator[key]
if !ok {
b = &bucket{dimensions: dimensions}
accumulator[key] = b
}
b.value += value
}
if err := rows.Err(); err != nil {
return errors.Wrapf(err, errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "iterate meter %q slice [%d, %d)", meterName, slice.StartMs, slice.EndMs)
}
return nil
}(); err != nil {
return nil, err
}
}
meters := make([]zeustypes.Meter, 0, len(accumulator))
for _, b := range accumulator {
meters = append(meters, zeustypes.NewMeter(MeterName, b.value, meterUnit, meterAggregation, window, b.dimensions))
}
// Empty windows still emit a sentinel so checkpoints can advance.
if len(meters) == 0 && len(slices) > 0 {
meters = append(meters, zeustypes.NewMeter(MeterName, 0, meterUnit, meterAggregation, window, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(slices[len(slices)-1].DefaultDays),
}))
}
return meters, nil
}
// buildQuery stays local because each meter owns its billing query.
func buildQuery(meterName string, slice retentiontypes.Slice, retentionGetter retention.Getter) (string, []any, []dimensionColumn, error) {
retentionExpr, err := retentionGetter.BuildMultiIfSQL(slice.Rules, slice.DefaultDays)
if err != nil {
return "", nil, nil, err
}
retentionRuleIndexExpr, err := retentionGetter.BuildRuleIndexSQL(slice.Rules)
if err != nil {
return "", nil, nil, err
}
columns, err := dimensionColumnsFor(retentionGetter, slice.Rules)
if err != nil {
return "", nil, nil, err
}
selects := make([]string, 0, len(columns)+3)
groupBy := make([]string, 0, len(columns)+2)
for _, column := range columns {
selects = append(selects, fmt.Sprintf("JSONExtractString(labels, '%s') AS %s", column.key, column.alias))
groupBy = append(groupBy, column.alias)
}
selects = append(selects,
retentionExpr+" AS retention_days",
retentionRuleIndexExpr+" AS retention_rule_index",
"ifNull(sum(value), 0) AS value",
)
groupBy = append(groupBy, "retention_days", "retention_rule_index")
sb := sqlbuilder.NewSelectBuilder()
sb.Select(selects...)
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
sb.Where(
sb.Equal("metric_name", meterName),
sb.GTE("unix_milli", slice.StartMs),
sb.LT("unix_milli", slice.EndMs),
)
sb.GroupBy(groupBy...)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
return query, args, columns, nil
}
type dimensionColumn struct {
key string
alias string
}
func dimensionColumnsFor(retentionGetter retention.Getter, rules []retentiontypes.CustomRetentionRule) ([]dimensionColumn, error) {
dimensionKeys, err := retentionGetter.RuleDimensionKeys(rules)
if err != nil {
return nil, err
}
keys := make([]string, 0, len(dimensionKeys)+1)
keys = append(keys, zeustypes.MeterDimensionWorkspaceKeyID)
for _, key := range dimensionKeys {
if key == zeustypes.MeterDimensionWorkspaceKeyID {
continue
}
keys = append(keys, key)
}
columns := make([]dimensionColumn, len(keys))
for i, key := range keys {
columns[i] = dimensionColumn{key: key, alias: fmt.Sprintf("dim_%d", i)}
}
return columns, nil
}
func buildDimensions(
orgID valuer.UUID,
retentionDays int,
retentionRuleIndex int,
columns []dimensionColumn,
values []string,
rules []retentiontypes.CustomRetentionRule,
) (map[string]string, error) {
if len(columns) != len(values) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "dimension column/value count mismatch: %d columns, %d values", len(columns), len(values))
}
valuesByKey := make(map[string]string, len(columns))
for i, column := range columns {
valuesByKey[column.key] = values[i]
}
dimensions := map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: strconv.Itoa(retentionDays),
}
addNonEmpty(dimensions, zeustypes.MeterDimensionWorkspaceKeyID, valuesByKey[zeustypes.MeterDimensionWorkspaceKeyID])
if retentionRuleIndex < 0 {
return dimensions, nil
}
if retentionRuleIndex >= len(rules) {
return nil, errors.Newf(errors.TypeInternal, zeustypes.ErrCodeMeterCollectFailed, "retention rule index %d out of range for %d rules", retentionRuleIndex, len(rules))
}
for _, filter := range rules[retentionRuleIndex].Filters {
addNonEmpty(dimensions, filter.Key, valuesByKey[filter.Key])
}
return dimensions, nil
}
func addNonEmpty(dimensions map[string]string, key, value string) {
if value == "" {
return
}
dimensions[key] = value
}
func bucketKey(dimensions map[string]string) string {
keys := make([]string, 0, len(dimensions))
for key := range dimensions {
keys = append(keys, key)
}
sort.Strings(keys)
var b strings.Builder
for _, key := range keys {
value := dimensions[key]
b.WriteString(strconv.Itoa(len(key)))
b.WriteByte(':')
b.WriteString(key)
b.WriteByte('=')
b.WriteString(strconv.Itoa(len(value)))
b.WriteByte(':')
b.WriteString(value)
b.WriteByte(';')
}
return b.String()
}

View File

@@ -1,58 +0,0 @@
package spansizemetercollector
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/retentiontypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestBuildDimensions(t *testing.T) {
orgID := valuer.GenerateUUID()
rules := []retentiontypes.CustomRetentionRule{{
Filters: []retentiontypes.FilterCondition{{
Key: "service.name",
Values: []string{"api"},
}},
TTLDays: 7,
}}
columns := []dimensionColumn{
{key: zeustypes.MeterDimensionWorkspaceKeyID, alias: "dim_0"},
{key: "service.name", alias: "dim_1"},
}
dimensions, err := buildDimensions(orgID, 30, 0, columns, []string{"workspace-1", "api"}, rules)
require.NoError(t, err)
require.Equal(t, map[string]string{
zeustypes.MeterDimensionOrganizationID: orgID.StringValue(),
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
}, dimensions)
}
func TestProviderMetadata(t *testing.T) {
provider := New(nil, nil)
require.Equal(t, "signoz.meter.span.size", provider.Name().String())
require.Equal(t, zeustypes.MeterUnitBytes, provider.Unit())
require.Equal(t, zeustypes.MeterAggregationSum, provider.Aggregation())
}
func TestBucketKeyIsStable(t *testing.T) {
first := bucketKey(map[string]string{
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
})
second := bucketKey(map[string]string{
zeustypes.MeterDimensionWorkspaceKeyID: "workspace-1",
"service.name": "api",
zeustypes.MeterDimensionRetentionDays: "30",
})
require.Equal(t, first, second)
require.NotEmpty(t, first)
}

View File

@@ -1,629 +0,0 @@
package httpmeterreporter
import (
"context"
"fmt"
"log/slog"
"sort"
"sync"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/meterreporter"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/telemetrymeter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/huandu/go-sqlbuilder"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)
var _ factory.ServiceWithHealthy = (*Provider)(nil)
var errCodeReportFailed = errors.MustNewCode("meterreporter_report_failed")
const (
phaseSealed = "sealed"
phaseToday = "today"
attrPhase = "phase"
attrResult = "result"
attrMeterReporterProvider = "meterreporter.provider"
attrOrgID = "meterreporter.org_id"
attrOrgCount = "meterreporter.org_count"
attrMeter = "meterreporter.meter"
attrDate = "meterreporter.date"
attrReadings = "meterreporter.readings"
attrReadingsCollected = "meterreporter.readings_collected"
attrReadingsDropped = "meterreporter.readings_dropped"
attrWindowStartUnixMilli = "meterreporter.window_start_unix_milli"
attrWindowEndUnixMilli = "meterreporter.window_end_unix_milli"
attrWindowCompleted = "meterreporter.window_completed"
attrCatchupStart = "meterreporter.catchup_start"
attrCatchupEnd = "meterreporter.catchup_end"
attrDurationMs = "meterreporter.duration_ms"
attrDryRun = "meterreporter.dry_run"
attrIdempotencyKey = "meterreporter.idempotency_key"
resultSuccess = "success"
resultFailure = "failure"
providerName = "http"
)
// Provider collects registered meters and ships them to Zeus.
type Provider struct {
settings factory.ScopedProviderSettings
config meterreporter.Config
collectors []metercollector.MeterCollector
licensing licensing.Licensing
telemetryStore telemetrystore.TelemetryStore
orgGetter organization.Getter
zeus zeus.Zeus
healthyC chan struct{}
stopC chan struct{}
goroutinesWg sync.WaitGroup
metrics *reporterMetrics
}
// NewFactory registers the HTTP meter reporter.
func NewFactory(
collectors map[zeustypes.MeterName]metercollector.MeterCollector,
licensing licensing.Licensing,
telemetryStore telemetrystore.TelemetryStore,
orgGetter organization.Getter,
zeus zeus.Zeus,
) factory.ProviderFactory[meterreporter.Reporter, meterreporter.Config] {
return factory.NewProviderFactory(
factory.MustNewName(providerName),
func(ctx context.Context, providerSettings factory.ProviderSettings, config meterreporter.Config) (meterreporter.Reporter, error) {
return newProvider(ctx, providerSettings, config, collectors, licensing, telemetryStore, orgGetter, zeus)
},
)
}
func newProvider(
_ context.Context,
providerSettings factory.ProviderSettings,
config meterreporter.Config,
collectors map[zeustypes.MeterName]metercollector.MeterCollector,
licensing licensing.Licensing,
telemetryStore telemetrystore.TelemetryStore,
orgGetter organization.Getter,
zeus zeus.Zeus,
) (*Provider, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/meterreporter/httpmeterreporter")
metrics, err := newReporterMetrics(settings.Meter())
if err != nil {
return nil, err
}
orderedCollectors, err := validateCollectors(collectors)
if err != nil {
return nil, err
}
return &Provider{
settings: settings,
config: config,
collectors: orderedCollectors,
licensing: licensing,
telemetryStore: telemetryStore,
orgGetter: orgGetter,
zeus: zeus,
healthyC: make(chan struct{}),
stopC: make(chan struct{}),
metrics: metrics,
}, nil
}
func validateCollectors(collectors map[zeustypes.MeterName]metercollector.MeterCollector) ([]metercollector.MeterCollector, error) {
ordered := make([]metercollector.MeterCollector, 0, len(collectors))
for name, collector := range collectors {
if name.IsZero() {
return nil, errors.New(errors.TypeInvalidInput, meterreporter.ErrCodeInvalidInput, "empty meter name in collector registry")
}
if collector == nil {
return nil, errors.Newf(errors.TypeInvalidInput, meterreporter.ErrCodeInvalidInput, "nil collector for meter %q", name.String())
}
if collector.Name() != name {
return nil, errors.Newf(errors.TypeInvalidInput, meterreporter.ErrCodeInvalidInput, "registry key %q does not match collector.Name() %q", name.String(), collector.Name().String())
}
if collector.Unit().IsZero() {
return nil, errors.Newf(errors.TypeInvalidInput, meterreporter.ErrCodeInvalidInput, "meter %q has empty unit", name.String())
}
if collector.Aggregation().IsZero() {
return nil, errors.Newf(errors.TypeInvalidInput, meterreporter.ErrCodeInvalidInput, "meter %q has empty aggregation", name.String())
}
ordered = append(ordered, collector)
}
sort.Slice(ordered, func(i, j int) bool {
return ordered[i].Name().String() < ordered[j].Name().String()
})
return ordered, nil
}
// Start runs an immediate tick, then repeats on Config.Interval.
func (provider *Provider) Start(ctx context.Context) error {
close(provider.healthyC)
provider.settings.Logger().InfoContext(ctx, "meter reporter started",
slog.Duration("interval", provider.config.Interval),
slog.Duration("timeout", provider.config.Timeout),
slog.Int("catchup_max_days_per_tick", provider.config.CatchupMaxDaysPerTick),
slog.Int("meters", len(provider.collectors)),
)
provider.goroutinesWg.Add(1)
go func() {
defer provider.goroutinesWg.Done()
provider.runTick(ctx)
ticker := time.NewTicker(provider.config.Interval)
defer ticker.Stop()
for {
select {
case <-provider.stopC:
return
case <-ticker.C:
provider.runTick(ctx)
}
}
}()
provider.goroutinesWg.Wait()
return nil
}
// Stop signals the tick loop and waits for any in-flight tick.
func (provider *Provider) Stop(ctx context.Context) error {
<-provider.healthyC
provider.settings.Logger().InfoContext(ctx, "meter reporter stopping")
select {
case <-provider.stopC:
// already closed
default:
close(provider.stopC)
}
provider.goroutinesWg.Wait()
provider.settings.Logger().InfoContext(ctx, "meter reporter stopped")
return nil
}
func (provider *Provider) Healthy() <-chan struct{} {
return provider.healthyC
}
// runTick executes one collect-and-ship cycle under Config.Timeout.
func (provider *Provider) runTick(parentCtx context.Context) {
tickStart := time.Now()
ctx, span := provider.settings.Tracer().Start(parentCtx, "meterreporter.Tick", trace.WithAttributes(
attribute.String(attrMeterReporterProvider, providerName),
attribute.Int("meterreporter.meters", len(provider.collectors)),
attribute.Int("meterreporter.catchup_max_days_per_tick", provider.config.CatchupMaxDaysPerTick),
))
defer span.End()
provider.metrics.ticks.Add(ctx, 1)
ctx, cancel := context.WithTimeout(ctx, provider.config.Timeout)
defer cancel()
provider.settings.Logger().DebugContext(ctx, "meter reporter tick started",
slog.Duration("timeout", provider.config.Timeout),
slog.Int("meters", len(provider.collectors)),
)
if err := provider.tick(ctx); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.SetAttributes(
attribute.String(attrResult, resultFailure),
attribute.Int64(attrDurationMs, time.Since(tickStart).Milliseconds()),
)
provider.settings.Logger().ErrorContext(ctx, "meter reporter tick failed",
errors.Attr(err),
slog.Duration("timeout", provider.config.Timeout),
slog.Duration("duration", time.Since(tickStart)),
)
return
}
span.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int64(attrDurationMs, time.Since(tickStart).Milliseconds()),
)
provider.settings.Logger().DebugContext(ctx, "meter reporter tick completed", slog.Duration("duration", time.Since(tickStart)))
}
// tick processes sealed catchup days, then today's partial window.
func (provider *Provider) tick(ctx context.Context) error {
now := time.Now().UTC()
// Use one timestamp so a tick cannot straddle midnight.
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
yesterday := todayStart.AddDate(0, 0, -1)
orgs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "failed to list organizations")
}
trace.SpanFromContext(ctx).SetAttributes(attribute.Int(attrOrgCount, len(orgs)))
if len(orgs) == 0 {
provider.settings.Logger().InfoContext(ctx, "skipping meter reporter tick; no organizations found")
return nil
}
org := orgs[0]
if len(orgs) > 1 {
// signoz_meter samples have no org marker.
provider.settings.Logger().WarnContext(ctx, "multiple orgs on a single instance; reporting only the first",
slog.Int("org_count", len(orgs)),
slog.String("selected_org_id", org.ID.StringValue()),
)
}
trace.SpanFromContext(ctx).SetAttributes(attribute.String(attrOrgID, org.ID.StringValue()))
license, err := provider.licensing.GetActive(ctx, org.ID)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "failed to fetch active license for org %q", org.ID.StringValue())
}
if license == nil || license.Key == "" {
provider.settings.Logger().WarnContext(ctx, "skipping tick, nil/empty license for org", slog.String("org_id", org.ID.StringValue()))
return nil
}
// TODO: re-enable once /v2/meters/checkpoints is live in staging. Until
// then we run with an empty checkpoint map; bootstrap floors are taken
// from data and dropCheckpointed becomes a no-op for the sealed window.
// checkpoints, err := provider.zeus.GetMeterCheckpoints(ctx, license.Key)
// if err != nil {
// provider.metrics.checkpointErrors.Add(ctx, 1)
// provider.settings.Logger().ErrorContext(ctx, "skipping tick: meter checkpoints call failed", errors.Attr(err))
// return nil
// }
// checkpointsByMeter := make(map[string]time.Time, len(checkpoints))
// for _, checkpoint := range checkpoints {
// checkpointsByMeter[checkpoint.Name] = checkpoint.Checkpoint.UTC()
// }
checkpointsByMeter := make(map[string]time.Time)
floor := provider.dataFloor(ctx, todayStart)
catchupStart := provider.catchupStart(floor, todayStart, checkpointsByMeter)
end := catchupStart.AddDate(0, 0, provider.config.CatchupMaxDaysPerTick-1)
if end.After(yesterday) {
end = yesterday
}
trace.SpanFromContext(ctx).SetAttributes(
attribute.String(attrCatchupStart, catchupStart.Format("2006-01-02")),
attribute.String(attrCatchupEnd, end.Format("2006-01-02")),
)
provider.settings.Logger().DebugContext(ctx, "meter reporter catchup window selected",
slog.String("org_id", org.ID.StringValue()),
slog.Time("data_floor", floor),
slog.Time("catchup_start", catchupStart),
slog.Time("catchup_end", end),
slog.Int("catchup_max_days_per_tick", provider.config.CatchupMaxDaysPerTick),
)
for day := catchupStart; !day.After(end); day = day.AddDate(0, 0, 1) {
window, err := zeustypes.NewMeterWindow(day.UnixMilli(), day.AddDate(0, 0, 1).UnixMilli(), true)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "build sealed meter window")
}
err = provider.runPhase(ctx, org.ID, license.Key, window, checkpointsByMeter)
result := resultSuccess
if err != nil {
result = resultFailure
}
provider.metrics.catchupDaysProcessed.Add(ctx, 1, metric.WithAttributes(attribute.String(attrResult, result)))
if err != nil {
provider.settings.Logger().WarnContext(ctx, "stopping sealed catchup after failed day",
errors.Attr(err),
slog.String("date", day.Format("2006-01-02")),
)
break
}
}
// Today's partial window runs every tick.
if now.UnixMilli() > todayStart.UnixMilli() {
todayWindow, err := zeustypes.NewMeterWindow(todayStart.UnixMilli(), now.UnixMilli(), false)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "build current-day meter window")
}
_ = provider.runPhase(ctx, org.ID, license.Key, todayWindow, checkpointsByMeter)
}
return nil
}
// runPhase collects all meters for one window and ships the batch.
func (provider *Provider) runPhase(ctx context.Context, orgID valuer.UUID, licenseKey string, window zeustypes.MeterWindow, checkpointsByMeter map[string]time.Time) error {
phaseLabel := phaseToday
if window.IsCompleted {
phaseLabel = phaseSealed
}
phaseAttr := metric.WithAttributes(attribute.String(attrPhase, phaseLabel))
date := time.UnixMilli(window.StartUnixMilli).UTC().Format("2006-01-02")
phaseStart := time.Now()
ctx, span := provider.settings.Tracer().Start(ctx, "meterreporter.RunPhase", trace.WithAttributes(
attribute.String(attrPhase, phaseLabel),
attribute.String(attrOrgID, orgID.StringValue()),
attribute.String(attrDate, date),
attribute.Int64(attrWindowStartUnixMilli, window.StartUnixMilli),
attribute.Int64(attrWindowEndUnixMilli, window.EndUnixMilli),
attribute.Bool(attrWindowCompleted, window.IsCompleted),
))
defer span.End()
provider.settings.Logger().DebugContext(ctx, "meter reporter phase started",
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Int64("start_unix_milli", window.StartUnixMilli),
slog.Int64("end_unix_milli", window.EndUnixMilli),
slog.Int("meters", len(provider.collectors)),
)
collectStart := time.Now()
readings := make([]zeustypes.Meter, 0, len(provider.collectors))
for _, collector := range provider.collectors {
meterName := collector.Name().String()
collectStart := time.Now()
collectCtx, collectSpan := provider.settings.Tracer().Start(ctx, "meterreporter.CollectMeter", trace.WithAttributes(
attribute.String(attrPhase, phaseLabel),
attribute.String(attrOrgID, orgID.StringValue()),
attribute.String(attrMeter, meterName),
attribute.String(attrDate, date),
attribute.Int64(attrWindowStartUnixMilli, window.StartUnixMilli),
attribute.Int64(attrWindowEndUnixMilli, window.EndUnixMilli),
attribute.Bool(attrWindowCompleted, window.IsCompleted),
))
collectedReadings, err := collector.Collect(collectCtx, orgID, window)
if err != nil {
collectSpan.RecordError(err)
collectSpan.SetStatus(codes.Error, err.Error())
collectSpan.SetAttributes(
attribute.String(attrResult, resultFailure),
attribute.Int64(attrDurationMs, time.Since(collectStart).Milliseconds()),
)
collectSpan.End()
provider.metrics.collectErrors.Add(ctx, 1, phaseAttr)
provider.settings.Logger().WarnContext(ctx, "meter collection failed",
errors.Attr(err),
slog.String("meter", meterName),
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Duration("duration", time.Since(collectStart)),
)
continue
}
collectSpan.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int(attrReadings, len(collectedReadings)),
attribute.Int64(attrDurationMs, time.Since(collectStart).Milliseconds()),
)
collectSpan.End()
provider.settings.Logger().DebugContext(ctx, "meter collection completed",
slog.String("meter", meterName),
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Int("readings", len(collectedReadings)),
slog.Duration("duration", time.Since(collectStart)),
)
readings = append(readings, collectedReadings...)
}
collectDuration := time.Since(collectStart)
provider.metrics.collectDuration.Add(ctx, collectDuration.Seconds(), phaseAttr)
provider.metrics.collectOperations.Add(ctx, 1, phaseAttr)
span.SetAttributes(attribute.Int(attrReadingsCollected, len(readings)))
if window.IsCompleted {
beforeDrop := len(readings)
readings = dropCheckpointed(readings, time.UnixMilli(window.StartUnixMilli).UTC(), checkpointsByMeter)
dropped := beforeDrop - len(readings)
span.SetAttributes(attribute.Int(attrReadingsDropped, dropped))
if dropped > 0 {
provider.settings.Logger().DebugContext(ctx, "dropped checkpointed meter readings",
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Int("dropped", dropped),
slog.Int("remaining", len(readings)),
)
}
}
if len(readings) == 0 {
span.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int(attrReadings, 0),
attribute.Int64(attrDurationMs, time.Since(phaseStart).Milliseconds()),
)
provider.settings.Logger().DebugContext(ctx, "meter reporter phase produced no readings",
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Duration("collect_duration", collectDuration),
slog.Duration("duration", time.Since(phaseStart)),
)
return nil
}
shipStart := time.Now()
err := provider.shipReadings(ctx, licenseKey, date, readings)
shipDuration := time.Since(shipStart)
provider.metrics.shipDuration.Add(ctx, shipDuration.Seconds(), phaseAttr)
provider.metrics.shipOperations.Add(ctx, 1, phaseAttr)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.SetAttributes(attribute.String(attrResult, resultFailure))
provider.metrics.postErrors.Add(ctx, 1, phaseAttr)
provider.settings.Logger().ErrorContext(ctx, "failed to ship meter readings",
errors.Attr(err),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Int("readings", len(readings)),
slog.Duration("ship_duration", shipDuration),
)
return err
}
provider.metrics.readingsEmitted.Add(ctx, int64(len(readings)), phaseAttr)
span.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int(attrReadings, len(readings)),
attribute.Int64(attrDurationMs, time.Since(phaseStart).Milliseconds()),
)
provider.settings.Logger().InfoContext(ctx, "meter reporter phase shipped",
slog.String("org_id", orgID.StringValue()),
slog.String("phase", phaseLabel),
slog.String("date", date),
slog.Int("readings", len(readings)),
slog.Duration("collect_duration", collectDuration),
slog.Duration("ship_duration", shipDuration),
slog.Duration("duration", time.Since(phaseStart)),
)
return nil
}
// dropCheckpointed removes readings already covered by meter checkpoints.
func dropCheckpointed(readings []zeustypes.Meter, windowDay time.Time, checkpointsByMeter map[string]time.Time) []zeustypes.Meter {
if len(checkpointsByMeter) == 0 {
return readings
}
kept := readings[:0]
for _, reading := range readings {
checkpoint, ok := checkpointsByMeter[reading.MeterName]
if !ok || checkpoint.Before(windowDay) {
kept = append(kept, reading)
}
}
return kept
}
// catchupStart returns the earliest UTC day that still needs sealed reporting.
func (provider *Provider) catchupStart(floor time.Time, todayStart time.Time, checkpointsByMeter map[string]time.Time) time.Time {
catchupStart := todayStart
for _, collector := range provider.collectors {
next := floor
if checkpoint, ok := checkpointsByMeter[collector.Name().String()]; ok {
next = checkpoint.AddDate(0, 0, 1)
if next.Before(floor) {
next = floor
}
}
if next.Before(catchupStart) {
catchupStart = next
}
}
yesterday := todayStart.AddDate(0, 0, -1)
if catchupStart.After(yesterday) {
catchupStart = yesterday
}
return catchupStart
}
// dataFloor returns the earliest signoz_meter sample day, or today on failure.
func (provider *Provider) dataFloor(ctx context.Context, todayStart time.Time) time.Time {
ctx, span := provider.settings.Tracer().Start(ctx, "meterreporter.DataFloor")
defer span.End()
if provider.telemetryStore == nil {
span.SetAttributes(attribute.String(attrResult, resultSuccess))
return todayStart
}
sb := sqlbuilder.NewSelectBuilder()
sb.Select("ifNull(min(unix_milli), 0)")
sb.From(telemetrymeter.DBName + "." + telemetrymeter.SamplesTableName)
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
var minMs int64
if err := provider.telemetryStore.ClickhouseDB().QueryRow(ctx, query, args...).Scan(&minMs); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.SetAttributes(attribute.String(attrResult, resultFailure))
provider.settings.Logger().WarnContext(ctx, "failed to read data floor; falling back to latest sealed day", errors.Attr(err))
return todayStart
}
if minMs == 0 {
span.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int64("meterreporter.data_floor_unix_milli", 0),
)
return todayStart
}
minDay := time.UnixMilli(minMs).UTC()
floor := time.Date(minDay.Year(), minDay.Month(), minDay.Day(), 0, 0, 0, 0, time.UTC)
span.SetAttributes(
attribute.String(attrResult, resultSuccess),
attribute.Int64("meterreporter.data_floor_unix_milli", floor.UnixMilli()),
)
provider.settings.Logger().DebugContext(ctx, "meter reporter data floor loaded", slog.Time("data_floor", floor))
return floor
}
// shipReadings sends one day's meter batch to Zeus.
func (provider *Provider) shipReadings(ctx context.Context, licenseKey string, date string, readings []zeustypes.Meter) error {
idempotencyKey := fmt.Sprintf("meter-cron:%s", date)
ctx, span := provider.settings.Tracer().Start(ctx, "meterreporter.ShipReadings", trace.WithAttributes(
attribute.String(attrDate, date),
attribute.Int(attrReadings, len(readings)),
attribute.String(attrIdempotencyKey, idempotencyKey),
attribute.Bool(attrDryRun, true),
))
defer span.End()
provider.settings.Logger().InfoContext(ctx, "meter readings prepared for shipment",
slog.String("date", date),
slog.Int("readings", len(readings)),
slog.String("idempotency_key", idempotencyKey),
slog.Bool("dry_run", true),
)
// Temporary visibility while /v2/meters is offline.
for _, reading := range readings {
provider.settings.Logger().InfoContext(ctx, "meter reading prepared for shipment",
slog.String("meter", reading.MeterName),
slog.Float64("value", reading.Value),
slog.String("unit", reading.Unit.StringValue()),
slog.String("aggregation", reading.Aggregation.StringValue()),
slog.Int64("start_unix_milli", reading.StartUnixMilli),
slog.Int64("end_unix_milli", reading.EndUnixMilli),
slog.Bool("is_completed", reading.IsCompleted),
slog.Any("dimensions", reading.Dimensions),
slog.String("idempotency_key", idempotencyKey),
)
}
// TODO: re-enable once /v2/meters is live in staging.
// body, err := json.Marshal(zeustypes.PostableMeters{Meters: readings})
// if err != nil {
// return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "marshal meter readings for %s", date)
// }
// if err := provider.zeus.PutMetersV3(ctx, licenseKey, idempotencyKey, body); err != nil {
// return errors.Wrapf(err, errors.TypeInternal, errCodeReportFailed, "ship meter readings for %s", date)
// }
_ = licenseKey
span.SetAttributes(attribute.String(attrResult, resultSuccess))
return nil
}

View File

@@ -1,103 +0,0 @@
package httpmeterreporter
import (
"context"
"testing"
"time"
"github.com/SigNoz/signoz/pkg/metercollector"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/require"
)
func TestValidateCollectorsRejectsBadRegistry(t *testing.T) {
meterA := zeustypes.MustNewMeterName("signoz.test.a")
meterB := zeustypes.MustNewMeterName("signoz.test.b")
t.Run("key name mismatch", func(t *testing.T) {
_, err := validateCollectors(map[zeustypes.MeterName]metercollector.MeterCollector{
meterA: testCollector{name: meterB},
})
require.Error(t, err)
})
t.Run("nil collector", func(t *testing.T) {
_, err := validateCollectors(map[zeustypes.MeterName]metercollector.MeterCollector{
meterA: nil,
})
require.Error(t, err)
})
}
func TestDropCheckpointed(t *testing.T) {
meterA := zeustypes.MustNewMeterName("signoz.test.a")
meterB := zeustypes.MustNewMeterName("signoz.test.b")
meterC := zeustypes.MustNewMeterName("signoz.test.c")
windowDay := time.Date(2026, 5, 4, 0, 0, 0, 0, time.UTC)
window := zeustypes.MustNewMeterWindow(windowDay.UnixMilli(), windowDay.AddDate(0, 0, 1).UnixMilli(), true)
readings := []zeustypes.Meter{
zeustypes.NewMeter(meterA, 0, zeustypes.MeterUnitCount, zeustypes.MeterAggregationSum, window, nil),
zeustypes.NewMeter(meterB, 0, zeustypes.MeterUnitCount, zeustypes.MeterAggregationSum, window, nil),
zeustypes.NewMeter(meterC, 0, zeustypes.MeterUnitCount, zeustypes.MeterAggregationSum, window, nil),
}
kept := dropCheckpointed(readings, windowDay, map[string]time.Time{
meterA.String(): windowDay,
meterB.String(): windowDay.AddDate(0, 0, -1),
})
require.Equal(t, []zeustypes.Meter{
zeustypes.NewMeter(meterB, 0, zeustypes.MeterUnitCount, zeustypes.MeterAggregationSum, window, nil),
zeustypes.NewMeter(meterC, 0, zeustypes.MeterUnitCount, zeustypes.MeterAggregationSum, window, nil),
}, kept)
}
func TestCatchupStart(t *testing.T) {
meterA := zeustypes.MustNewMeterName("signoz.test.a")
floor := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
todayStart := time.Date(2026, 5, 5, 0, 0, 0, 0, time.UTC)
provider := &Provider{
collectors: []metercollector.MeterCollector{
testCollector{name: meterA},
},
}
t.Run("no checkpoint starts at floor", func(t *testing.T) {
require.Equal(t, floor, provider.catchupStart(floor, todayStart, nil))
})
t.Run("checkpoint advances by one day", func(t *testing.T) {
require.Equal(t, floor.AddDate(0, 0, 2), provider.catchupStart(floor, todayStart, map[string]time.Time{
meterA.String(): floor.AddDate(0, 0, 1),
}))
})
}
type testCollector struct {
name zeustypes.MeterName
unit zeustypes.MeterUnit
aggregation zeustypes.MeterAggregation
}
func (c testCollector) Name() zeustypes.MeterName {
return c.name
}
func (c testCollector) Unit() zeustypes.MeterUnit {
if c.unit.IsZero() {
return zeustypes.MeterUnitCount
}
return c.unit
}
func (c testCollector) Aggregation() zeustypes.MeterAggregation {
if c.aggregation.IsZero() {
return zeustypes.MeterAggregationSum
}
return c.aggregation
}
func (c testCollector) Collect(context.Context, valuer.UUID, zeustypes.MeterWindow) ([]zeustypes.Meter, error) {
return nil, nil
}

View File

@@ -1,90 +0,0 @@
package httpmeterreporter
import (
"github.com/SigNoz/signoz/pkg/errors"
"go.opentelemetry.io/otel/metric"
)
type reporterMetrics struct {
ticks metric.Int64Counter
readingsEmitted metric.Int64Counter
collectErrors metric.Int64Counter
postErrors metric.Int64Counter
checkpointErrors metric.Int64Counter
catchupDaysProcessed metric.Int64Counter
collectDuration metric.Float64Counter
collectOperations metric.Int64Counter
shipDuration metric.Float64Counter
shipOperations metric.Int64Counter
}
func newReporterMetrics(meter metric.Meter) (*reporterMetrics, error) {
var errs error
ticks, err := meter.Int64Counter("signoz.meterreporter.ticks", metric.WithDescription("Meter reporter ticks."))
if err != nil {
errs = errors.Join(errs, err)
}
readingsEmitted, err := meter.Int64Counter("signoz.meterreporter.readings.emitted", metric.WithDescription("Meter readings shipped to Zeus."))
if err != nil {
errs = errors.Join(errs, err)
}
collectErrors, err := meter.Int64Counter("signoz.meterreporter.collect.errors", metric.WithDescription("Meter collection errors."))
if err != nil {
errs = errors.Join(errs, err)
}
postErrors, err := meter.Int64Counter("signoz.meterreporter.post.errors", metric.WithDescription("Zeus POST failures."))
if err != nil {
errs = errors.Join(errs, err)
}
checkpointErrors, err := meter.Int64Counter("signoz.meterreporter.checkpoint.errors", metric.WithDescription("Zeus checkpoint read failures."))
if err != nil {
errs = errors.Join(errs, err)
}
catchupDaysProcessed, err := meter.Int64Counter("signoz.meterreporter.catchup.days_processed", metric.WithDescription("Sealed catchup days processed."))
if err != nil {
errs = errors.Join(errs, err)
}
collectDuration, err := meter.Float64Counter("signoz.meterreporter.collect.duration.seconds", metric.WithDescription("Cumulative collection duration."), metric.WithUnit("s"))
if err != nil {
errs = errors.Join(errs, err)
}
collectOperations, err := meter.Int64Counter("signoz.meterreporter.collect.operations", metric.WithDescription("Collection phases measured."))
if err != nil {
errs = errors.Join(errs, err)
}
shipDuration, err := meter.Float64Counter("signoz.meterreporter.ship.duration.seconds", metric.WithDescription("Cumulative ship duration."), metric.WithUnit("s"))
if err != nil {
errs = errors.Join(errs, err)
}
shipOperations, err := meter.Int64Counter("signoz.meterreporter.ship.operations", metric.WithDescription("Ship phases measured."))
if err != nil {
errs = errors.Join(errs, err)
}
if errs != nil {
return nil, errs
}
return &reporterMetrics{
ticks: ticks,
readingsEmitted: readingsEmitted,
collectErrors: collectErrors,
postErrors: postErrors,
checkpointErrors: checkpointErrors,
catchupDaysProcessed: catchupDaysProcessed,
collectDuration: collectDuration,
collectOperations: collectOperations,
shipDuration: shipDuration,
shipOperations: shipOperations,
}, nil
}

View File

@@ -150,72 +150,6 @@ func (provider *Provider) PutMetersV2(ctx context.Context, key string, data []by
return err
}
func (provider *Provider) PutMetersV3(ctx context.Context, key string, idempotencyKey string, data []byte) error {
headers := http.Header{}
if idempotencyKey != "" {
headers.Set("X-Idempotency-Key", idempotencyKey)
}
_, err := provider.doWithHeaders(
ctx,
provider.config.URL.JoinPath("/v2/meters"),
http.MethodPost,
key,
data,
headers,
)
return err
}
func (provider *Provider) GetMeterCheckpoints(ctx context.Context, key string) ([]zeustypes.MeterCheckpoint, error) {
response, err := provider.do(
ctx,
provider.config.URL.JoinPath("/v2/meters/checkpoints"),
http.MethodGet,
key,
nil,
)
if err != nil {
return nil, err
}
checkpointValues := gjson.GetBytes(response, "data.checkpoints")
if !checkpointValues.Exists() || checkpointValues.Type == gjson.Null {
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoints are required")
}
if !checkpointValues.IsArray() {
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoints must be an array")
}
checkpointResults := checkpointValues.Array()
checkpoints := make([]zeustypes.MeterCheckpoint, 0, len(checkpointResults))
for _, checkpointValue := range checkpointResults {
name := checkpointValue.Get("name").String()
if name == "" {
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoint name is required")
}
checkpointString := checkpointValue.Get("checkpoint").String()
if checkpointString == "" {
return nil, errors.Newf(errors.TypeInternal, zeus.ErrCodeResponseMalformed, "meter checkpoint is required for %q", name)
}
checkpoint, err := time.Parse("2006-01-02", checkpointString)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, zeus.ErrCodeResponseMalformed, "parse meter checkpoint %q for %q", checkpointString, name)
}
checkpoints = append(checkpoints, zeustypes.MeterCheckpoint{
Name: name,
Checkpoint: checkpoint,
})
}
return checkpoints, nil
}
func (provider *Provider) PutProfile(ctx context.Context, key string, profile *zeustypes.PostableProfile) error {
body, err := json.Marshal(profile)
if err != nil {
@@ -251,21 +185,12 @@ func (provider *Provider) PutHost(ctx context.Context, key string, host *zeustyp
}
func (provider *Provider) do(ctx context.Context, url *url.URL, method string, key string, requestBody []byte) ([]byte, error) {
return provider.doWithHeaders(ctx, url, method, key, requestBody, nil)
}
func (provider *Provider) doWithHeaders(ctx context.Context, url *url.URL, method string, key string, requestBody []byte, extraHeaders http.Header) ([]byte, error) {
request, err := http.NewRequestWithContext(ctx, method, url.String(), bytes.NewBuffer(requestBody))
if err != nil {
return nil, err
}
request.Header.Set("X-Signoz-Cloud-Api-Key", key)
request.Header.Set("Content-Type", "application/json")
for k, vs := range extraHeaders {
for _, v := range vs {
request.Header.Add(k, v)
}
}
response, err := provider.httpClient.Do(request)
if err != nil {

View File

@@ -289,6 +289,8 @@
// Prevents navigator.clipboard - use useCopyToClipboard hook instead (disabled in tests via override)
"signoz/no-raw-absolute-path": "error",
// Prevents window.open(path), window.location.origin + path, window.location.href = path
"signoz/no-antd-components": "error",
// Prevents the usage of specific antd components in favor of our lib
"no-restricted-globals": [
"error",
{

View File

@@ -50,7 +50,7 @@
"@signozhq/design-tokens": "2.1.4",
"@signozhq/icons": "0.1.0",
"@signozhq/resizable": "0.0.2",
"@signozhq/ui": "0.0.12",
"@signozhq/ui": "0.0.14",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -0,0 +1,66 @@
/**
* Rule: no-antd-components
*
* Prevents importing specific components from antd.
*
* This rule catches patterns like:
* import { Typography } from 'antd'
* import { Typography, Button } from 'antd'
* import Typography from 'antd/es/typography'
* import { Text } from 'antd/es/typography'
*
* Add components to BANNED_COMPONENTS to ban them.
* Key should be PascalCase component name, will match lowercase path too.
*/
const BANNED_COMPONENTS = {
Typography: 'Use @signozhq/ui Typography instead of antd Typography.',
};
export default {
create(context) {
return {
ImportDeclaration(node) {
const source = node.source.value;
// Check direct antd import: import { Typography } from 'antd'
if (source === 'antd') {
for (const specifier of node.specifiers) {
if (specifier.type !== 'ImportSpecifier') {
continue;
}
const importedName = specifier.imported.name;
const message = BANNED_COMPONENTS[importedName];
if (message) {
context.report({
node: specifier,
message: `Do not import '${importedName}' from antd. ${message}`,
});
}
}
return;
}
// Check antd/es/<component> import: import Typography from 'antd/es/typography'
const match = source.match(/^antd\/es\/([^/]+)/);
if (!match) {
return;
}
const pathComponent = match[1].toLowerCase();
for (const [componentName, message] of Object.entries(BANNED_COMPONENTS)) {
if (pathComponent === componentName.toLowerCase()) {
context.report({
node,
message: `Do not import from '${source}'. ${message}`,
});
break;
}
}
},
};
},
};

View File

@@ -9,6 +9,7 @@ import noZustandGetStateInHooks from './rules/no-zustand-getstate-in-hooks.mjs';
import noNavigatorClipboard from './rules/no-navigator-clipboard.mjs';
import noUnsupportedAssetPattern from './rules/no-unsupported-asset-pattern.mjs';
import noRawAbsolutePath from './rules/no-raw-absolute-path.mjs';
import noAntdComponents from './rules/no-antd-components.mjs';
export default {
meta: {
@@ -19,5 +20,6 @@ export default {
'no-navigator-clipboard': noNavigatorClipboard,
'no-unsupported-asset-pattern': noUnsupportedAssetPattern,
'no-raw-absolute-path': noRawAbsolutePath,
'no-antd-components': noAntdComponents,
},
};

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import get from 'api/browser/localstorage/get';
import { LOCALSTORAGE } from 'constants/localStorage';
import { THEME_MODE } from 'hooks/useDarkMode/constant';

View File

@@ -14,8 +14,8 @@ import {
TableColumnsType,
TableColumnType,
Tooltip,
Typography,
} from 'antd';
import { Typography } from '@signozhq/ui';
import type { FilterDropdownProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import {

View File

@@ -1,5 +1,6 @@
import { useHistory, useLocation } from 'react-router-dom';
import { Select, Spin, Typography } from 'antd';
import { Select, Spin } from 'antd';
import { Typography } from '@signozhq/ui';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Divider, Drawer, Typography } from 'antd';
import { Divider, Drawer } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { PANEL_TYPES } from 'constants/queryBuilder';
import dayjs from 'dayjs';

View File

@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Card, Typography } from 'antd';
import { Card } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { CardContainer } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';

View File

@@ -1,7 +1,8 @@
import { useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal, Typography } from 'antd';
import { Button, Modal } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import { useNotifications } from 'hooks/useNotifications';

View File

@@ -554,10 +554,11 @@ function ClientSideQBSearch(
>
<Tooltip title={chipValue}>
<TypographyText
ellipsis
role="button"
tabIndex={0}
$isInNin={isInNin}
disabled={isDisabled}
$isEnabled={!!searchValue}
$disabled={isDisabled}
onClick={(): void => {
if (!isDisabled) {
tagEditHandler(value);

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Radio, Tooltip, Typography } from 'antd';
import { Button, Popover, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
import { Download, DownloadIcon, Loader2 } from 'lucide-react';

View File

@@ -15,8 +15,8 @@ import {
Row,
Select,
Space,
Typography,
} from 'antd';
import { Typography } from '@signozhq/ui';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';

View File

@@ -1,6 +1,7 @@
import { MouseEvent, useCallback } from 'react';
import { DeleteOutlined } from '@ant-design/icons';
import { Col, Row, Tooltip, Typography } from 'antd';
import { Col, Row, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
@@ -81,7 +82,7 @@ function MenuItemGenerator({
</Tooltip>
</Row>
<Row>
<Typography.Text type="secondary">Created by {createdBy}</Typography.Text>
<Typography.Text color="muted">Created by {createdBy}</Typography.Text>
</Row>
</Col>
<Col span={2}>

View File

@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Card, Form, Input, Typography } from 'antd';
import { Card, Form, Input } from 'antd';
import { Typography } from '@signozhq/ui';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSaveView } from 'hooks/saveViews/useSaveView';

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
function AnnouncementsModal(): JSX.Element {
return (

View File

@@ -1,7 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui';
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
@@ -135,12 +136,14 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
<div className="feedback-modal-content-footer-info-text">
<Typography.Text>
Have a specific issue?{' '}
<Typography.Link
<a
role="button"
tabIndex={0}
className="contact-support-link"
onClick={handleContactSupportClick}
>
Contact Support{' '}
</Typography.Link>
</a>
or{' '}
<a
href="https://signoz.io/docs/introduction/"

View File

@@ -4,7 +4,8 @@ import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { Button, Switch, Typography } from 'antd';
import { Button, Switch } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';

View File

@@ -1,7 +1,8 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Color } from '@signozhq/design-tokens';
import { Button, Typography } from 'antd';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { useNotifications } from 'hooks/useNotifications';
import { CheckCircle2, HandPlatter } from 'lucide-react';

View File

@@ -1,5 +1,6 @@
import { useState } from 'react';
import { Button, Input, Typography } from 'antd';
import { Button, Input } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import { X } from 'lucide-react';

View File

@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Modal, Tooltip, Typography } from 'antd';
import { Button, Modal, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
import cx from 'classnames';

View File

@@ -4,7 +4,8 @@ import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui';
import { Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
import { Divider, Drawer, Radio, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import type { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
@@ -587,7 +588,7 @@ function LogDetailInner({
<div className="log-detail-drawer__footer-hint">
<div className="log-detail-drawer__footer-hint-content">
<Typography.Text
type="secondary"
color="muted"
className="log-detail-drawer__footer-hint-text"
>
Use
@@ -596,7 +597,7 @@ function LogDetailInner({
<span>/</span>
<ArrowDown size={14} className="log-detail-drawer__footer-hint-icon" />
<Typography.Text
type="secondary"
color="muted"
className="log-detail-drawer__footer-hint-text"
>
to view previous/next log

View File

@@ -6,7 +6,7 @@ interface ICategoryHeadingProps {
children: ReactNode;
}
function CategoryHeading({ children }: ICategoryHeadingProps): JSX.Element {
return <CategoryHeadingText type="secondary">{children}</CategoryHeadingText>;
return <CategoryHeadingText color="muted">{children}</CategoryHeadingText>;
}
export default CategoryHeading;

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import styled from 'styled-components';
export const CategoryHeadingText = styled(Typography.Text)`

View File

@@ -1,6 +1,6 @@
import { memo, useCallback, useMemo } from 'react';
import { blue } from '@ant-design/colors';
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import { VIEW_TYPES } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -89,7 +89,7 @@ function LogSelectedField({
</span>
</Typography.Text>
</AddToQueryHOC>
<Typography.Text ellipsis className={cx('selected-log-kv', fontSize)}>
<Typography.Text truncate={1} className={cx('selected-log-kv', fontSize)}>
<span className={cx('selected-log-field-key', fontSize)}>{': '}</span>
<span className={cx('selected-log-value', fontSize)}>
{fieldValue || "''"}

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Input, InputNumber, Popover, Tooltip, Typography } from 'antd';
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import type { DefaultOptionType } from 'antd/es/select';
import cx from 'classnames';
import { LogViewMode } from 'container/LogsTable';

View File

@@ -1,15 +1,8 @@
import { Typography } from '@signozhq/ui';
import { ReactNode, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { CaretDownOutlined, LoadingOutlined } from '@ant-design/icons';
import {
Modal,
Select,
Spin,
Tooltip,
Tree,
TreeDataNode,
Typography,
} from 'antd';
import { Modal, Select, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -84,9 +77,11 @@ function ErrorTitleAndKey({
key: `${title}-key-${uuid()}`,
title: (
<div className="attribute-error-title">
<Typography.Text className="tree-text" ellipsis={{ tooltip: title }}>
{title}
</Typography.Text>
<Tooltip title={title}>
<Typography.Text className="tree-text" truncate={1}>
{title}
</Typography.Text>
</Tooltip>
<Tooltip title={errorMsg}>
<div
className="attribute-error-warning"
@@ -125,9 +120,11 @@ function treeTitleAndKey({
key: `${title}-key-${uuid()}`,
title: (
<div className="attribute-success-title">
<Typography.Text className="tree-text" ellipsis={{ tooltip: title }}>
{title}
</Typography.Text>
<Tooltip title={title}>
<Typography.Text className="tree-text" truncate={1}>
{title}
</Typography.Text>
</Tooltip>
{isLeaf && (
<div className="success-attribute-icon">
<Tooltip title="Success">

View File

@@ -13,7 +13,8 @@ import {
ReloadOutlined,
} from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Checkbox, Select, Typography } from 'antd';
import { Button, Checkbox, Select } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -755,15 +756,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}}
>
<div className="option-content">
<Typography.Text
ellipsis={{
tooltip: {
placement: 'right',
autoAdjustOverflow: true,
},
}}
className="option-label-text"
>
<Typography.Text truncate={1} className="option-label-text">
{highlightMatchedText(String(option.label || ''), searchText)}
</Typography.Text>
{(option.type === 'custom' || option.type === 'regex') && (

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Button, Tooltip, Typography } from 'antd';
import { Button, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import WarningPopover from 'components/WarningPopover/WarningPopover';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';

View File

@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { Button, Tooltip, Typography } from 'antd';
import { Button, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import eyesEmojiUrl from 'assets/Images/eyesEmoji.svg';
import styles from './QueryCancelledPlaceholder.module.scss';

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Button, Checkbox, Input, Skeleton, Typography } from 'antd';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
import {
@@ -574,7 +575,9 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
</section>
<section className="right-action">
{isOpen && !!attributeValues.length && (
<Typography.Text
<span
role="button"
tabIndex={0}
className="clear-all"
onClick={(e): void => {
e.stopPropagation();
@@ -583,7 +586,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
}}
>
Clear All
</Typography.Text>
</span>
)}
</section>
</section>
@@ -640,16 +643,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
{filter.customRendererForValue ? (
filter.customRendererForValue(value)
) : (
<Typography.Text
className="value-string"
ellipsis={{
tooltip: {
placement: 'top',
mouseEnterDelay: 0.2,
mouseLeaveDelay: 0,
},
}}
>
<Typography.Text className="value-string" truncate={1}>
{String(value)}
</Typography.Text>
)}
@@ -677,12 +671,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
)}
{visibleItemsCount < attributeValues?.length && (
<section className="show-more">
<Typography.Text
<span
role="button"
tabIndex={0}
className="show-more-text"
onClick={(): void => setVisibleItemsCount((prev) => prev + 10)}
>
Show More...
</Typography.Text>
</span>
</section>
)}
</>

View File

@@ -12,7 +12,8 @@ import {
ComboboxList,
ComboboxTrigger,
} from '@signozhq/ui';
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
import { Skeleton, Switch, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import Time from './Time';

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { useTimezone } from 'providers/Timezone';

View File

@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import { Popover, Typography } from 'antd';
import { Popover } from 'antd';
import { Typography } from '@signozhq/ui';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import dayjs from 'dayjs';

View File

@@ -11,10 +11,12 @@ import {
Space,
SpaceProps,
TabsProps,
Typography,
} from 'antd';
import type { TextProps } from 'antd/lib/typography/Text';
import type { TitleProps } from 'antd/lib/typography/Title';
import {
Typography,
TypographyTextProps,
TypographyTitleProps,
} from '@signozhq/ui';
import styled, { FlattenSimpleInterpolation } from 'styled-components';
import { IStyledClass } from './types';
@@ -53,13 +55,13 @@ const StyledButton = styled(Button)<TStyledButton>`
`;
const { Text } = Typography;
type TStyledTypographyText = TextProps & IStyledClass;
type TStyledTypographyText = TypographyTextProps & IStyledClass;
const StyledTypographyText = styled(Text)<TStyledTypographyText>`
${styledClass}
`;
const { Title } = Typography;
type TStyledTypographyTitle = TitleProps & IStyledClass;
type TStyledTypographyTitle = TypographyTitleProps & IStyledClass;
const StyledTypographyTitle = styled(Title)<TStyledTypographyTitle>`
${styledClass}
`;

View File

@@ -1,4 +1,4 @@
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import { timeItems } from 'container/NewWidget/RightContainer/timeItems';
export const menuItems = timeItems.map((item) => ({

View File

@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { DownOutlined } from '@ant-design/icons';
import { Button, Dropdown, Typography } from 'antd';
import { Button, Dropdown } from 'antd';
import { Typography } from '@signozhq/ui';
import TimeItems, {
timePreferance,
timePreferenceType,

View File

@@ -8,7 +8,7 @@ import {
useRef,
} from 'react';
import * as Sentry from '@sentry/react';
import { Typography } from 'antd';
import { Typography } from '@signozhq/ui';
import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';

View File

@@ -1,7 +1,8 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import { Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { getBackgroundColorAndThresholdCheck } from './utils';

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { PlusOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import { Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner';
@@ -21,7 +22,7 @@ import { Button, ButtonContainer, RightActionContainer } from './styles';
import './AllAlertChannels.styles.scss';
const { Paragraph } = Typography;
const { Text } = Typography;
function AlertChannels(): JSX.Element {
const { t } = useTranslation(['channels']);
@@ -60,9 +61,9 @@ function AlertChannels(): JSX.Element {
return (
<div className="alert-channels-container">
<ButtonContainer>
<Paragraph ellipsis type="secondary">
<Text truncate={1} color="muted">
{t('sending_channels_note')}
</Paragraph>
</Text>
<RightActionContainer>
<TextToolTip

View File

@@ -1,3 +1,4 @@
import { Typography } from '@signozhq/ui';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
@@ -5,15 +6,7 @@ import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom';
import { SearchOutlined } from '@ant-design/icons';
import {
Button,
Card,
Input,
Space,
TableProps,
Tooltip,
Typography,
} from 'antd';
import { Button, Card, Input, Space, TableProps, Tooltip } from 'antd';
import type { ColumnType, TablePaginationConfig } from 'antd/es/table';
import type { FilterValue, SorterResult } from 'antd/es/table/interface';
import type { ColumnsType } from 'antd/lib/table';
@@ -360,13 +353,7 @@ function AllErrors(): JSX.Element {
width: 100,
render: (value): JSX.Element => (
<Tooltip overlay={(): JSX.Element => value}>
<Typography.Paragraph
ellipsis={{
rows: 2,
}}
>
{value}
</Typography.Paragraph>
<Typography.Text truncate={2}>{value}</Typography.Text>
</Tooltip>
),
},

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { Checkbox, Input, Typography } from 'antd';
import { Checkbox, Input } from 'antd';
import { Typography } from '@signozhq/ui';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useResizeObserver } from 'hooks/useDimensions';

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio, Typography } from 'antd';
import { Button, Divider, Drawer, Radio } from 'antd';
import { Typography } from '@signozhq/ui';
import type { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {

View File

@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { QueryFunctionContext, useQueries, useQuery } from 'react-query';
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Switch, Table, Tooltip, Typography } from 'antd';
import { Spin, Switch, Table, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { getQueryRangeV5 } from 'api/v5/queryRange/getQueryRange';
import { MetricRangePayloadV5, ScalarData } from 'api/v5/v5';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';

View File

@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { Skeleton, Table, TablePaginationConfig, Typography } from 'antd';
import { Skeleton, Table, TablePaginationConfig } from 'antd';
import { Typography } from '@signozhq/ui';
import { QueryParams } from 'constants/query';
import {
dependentServicesColumns,

View File

@@ -1,7 +1,8 @@
import { useMemo } from 'react';
import { useQueries } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Progress, Skeleton, Tooltip, Typography } from 'antd';
import { Progress, Skeleton, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
@@ -87,28 +88,16 @@ function DomainMetrics({
<div className="domain-detail-drawer__endpoint">
<div className="domain-details-grid">
<div className="labels-row">
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
EXTERNAL API
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
AVERAGE LATENCY
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
ERROR %
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
LAST USED
</Typography.Text>
</div>

View File

@@ -1,7 +1,8 @@
import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Progress, Skeleton, Tooltip, Typography } from 'antd';
import { Progress, Skeleton, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import {
getDisplayValue,
getFormattedEndPointMetricsData,
@@ -37,28 +38,16 @@ function EndPointMetrics({
<div className="domain-detail-drawer__endpoint">
<div className="domain-details-grid">
<div className="labels-row">
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
Rate
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
AVERAGE LATENCY
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
ERROR %
</Typography.Text>
<Typography.Text
type="secondary"
className="domain-details-metadata-label"
>
<Typography.Text color="muted" className="domain-details-metadata-label">
LAST USED
</Typography.Text>
</div>

View File

@@ -1,4 +1,5 @@
import { Button, Typography } from 'antd';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui';
import { RotateCw } from 'lucide-react';
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
@@ -13,7 +14,7 @@ function ErrorState({ refetch }: { refetch: () => void }): JSX.Element {
</div>
<div className="error-state-text">
<Typography.Text>Uh-oh :/ We ran into an error.</Typography.Text>
<Typography.Text type="secondary">
<Typography.Text color="muted">
Please refresh this panel.
</Typography.Text>
</div>

View File

@@ -1,7 +1,8 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { Color } from '@signozhq/design-tokens';
import { Button, Card, Skeleton, Typography } from 'antd';
import { Button, Card, Skeleton } from 'antd';
import { Typography } from '@signozhq/ui';
import cx from 'classnames';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
import {

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { Table, Typography } from 'antd';
import { Table } from 'antd';
import { Typography } from '@signozhq/ui';
import {
endPointStatusCodeColumns,
getFormattedEndPointStatusCodeData,

View File

@@ -1,3 +1,4 @@
import { Typography } from '@signozhq/ui';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQuery } from 'react-query';
@@ -14,7 +15,6 @@ import {
Table,
TableColumnsType as ColumnsType,
Tag,
Typography,
} from 'antd';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import logEvent from 'api/common/logEvent';
@@ -362,15 +362,19 @@ export default function BillingContainer(): JSX.Element {
[apiResponse, billAmount, isLoading, isFetchingBillingData],
);
const { Text } = Typography;
const subscriptionPastDueMessage = (): JSX.Element => (
<Typography>
{`We were not able to process payments for your account. Please update your card details `}
<Text type="danger" onClick={handleBilling} style={{ cursor: 'pointer' }}>
<a
role="button"
tabIndex={0}
onClick={handleBilling}
style={{ cursor: 'pointer', color: 'var(--bg-cherry-500)' }}
>
{t('here')}
</Text>
</a>
{` if your payment information has changed. Email us at `}
<Text type="secondary">cloud-support@signoz.io</Text>
<Typography.Text color="muted">cloud-support@signoz.io</Typography.Text>
{` otherwise. Be sure to provide this information immediately to avoid interruption to your service.`}
</Typography>
);
@@ -418,7 +422,7 @@ export default function BillingContainer(): JSX.Element {
<Typography.Text style={{ fontWeight: 500, fontSize: 18 }}>
{t('billing')}
</Typography.Text>
<Typography.Text color={Color.BG_VANILLA_400}>
<Typography.Text color="muted">
{t('manage_billing_and_costs')}
</Typography.Text>
</Flex>
@@ -472,7 +476,7 @@ export default function BillingContainer(): JSX.Element {
{trialInfo?.onTrial && trialInfo?.trialConvertedToSubscription && (
<Typography.Text
ellipsis
truncate={1}
style={{ fontWeight: '300', color: 'var(--bg-forest-500)', fontSize: 12 }}
>
{t('card_details_recieved_and_billing_info')}

View File

@@ -1,6 +1,7 @@
import { useMemo, useRef } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Card, Flex, Typography } from 'antd';
import { Card, Flex } from 'antd';
import { Typography } from '@signozhq/ui';
import Uplot from 'components/Uplot';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
@@ -208,7 +209,7 @@ export function BillingUsageGraph(props: BillingUsageGraphProps): JSX.Element {
<Typography.Text className="total-spent-title">
TOTAL SPENT
</Typography.Text>
<Typography.Text color={Color.BG_VANILLA_100} className="total-spent">
<Typography.Text className="total-spent">
${numberFormatter.format(billAmount)}
</Typography.Text>
</Flex>

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Row, Tag, Typography } from 'antd';
import { Row, Tag } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features';
@@ -78,7 +79,9 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
data-testid={`alert-type-card-${option.selection}`}
>
{option.description}{' '}
<Typography.Link
<a
role="button"
tabIndex={0}
onClick={(e): void => {
e.preventDefault();
e.stopPropagation();
@@ -86,7 +89,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
}}
>
Click here to see how to create a sample alert.
</Typography.Link>{' '}
</a>{' '}
</AlertTypeCard>
))}
</>

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip, Typography } from 'antd';
import { Button, Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import getRandomColor from 'lib/getRandomColor';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Select, Typography } from 'antd';
import { Select } from 'antd';
import { Typography } from '@signozhq/ui';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useAppContext } from 'providers/App/App';

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react';
import { Button, Input, Select, Tooltip, Typography } from 'antd';
import { Button, Input, Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { CircleX, Trash } from 'lucide-react';
import { useAppContext } from 'providers/App/App';

View File

@@ -1,4 +1,5 @@
import { Button, Flex, SelectProps, Switch, Typography } from 'antd';
import { Button, Flex, SelectProps, Switch } from 'antd';
import { Typography } from '@signozhq/ui';
import type { BaseOptionType, DefaultOptionType } from 'antd/es/select';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
import { YAxisSource } from 'components/YAxisUnitSelector/types';

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Switch, Tooltip, Typography } from 'antd';
import { Switch, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { Info } from 'lucide-react';
import { IAdvancedOptionItemProps } from '../types';

View File

@@ -1,4 +1,5 @@
import { Collapse, Input, Typography } from 'antd';
import { Collapse, Input } from 'antd';
import { Typography } from '@signozhq/ui';
import { useCreateAlertState } from '../context';
import AdvancedOptionItem from './AdvancedOptionItem';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Button, Typography } from 'antd';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui';
import { useCreateAlertState } from 'container/CreateAlertV2/context';
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
import { IEditCustomScheduleProps } from 'container/CreateAlertV2/EvaluationSettings/types';

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Input, Select, Tooltip, Typography } from 'antd';
import { Input, Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { Info } from 'lucide-react';
import { useCreateAlertState } from '../../context';

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Button, DatePicker, Input, Select, Typography } from 'antd';
import { Button, DatePicker, Input, Select } from 'antd';
import { Typography } from '@signozhq/ui';
import classNames from 'classnames';
import { useCreateAlertState } from 'container/CreateAlertV2/context';
import { AdvancedOptionsState } from 'container/CreateAlertV2/context/types';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Modal, Typography } from 'antd';
import { Modal } from 'antd';
import { Typography } from '@signozhq/ui';
import { Calendar, Info } from 'lucide-react';
import { useCreateAlertState } from '../../context';

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Input, Select, Typography } from 'antd';
import { Input, Select } from 'antd';
import { Typography } from '@signozhq/ui';
import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
import {

View File

@@ -1,4 +1,5 @@
import { Button, Typography } from 'antd';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui';
import classNames from 'classnames';
import { Check } from 'lucide-react';

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Select, Tooltip, Typography } from 'antd';
import { Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Info } from 'lucide-react';
@@ -98,9 +99,9 @@ function MultipleNotifications(): JSX.Element {
data-testid="multiple-notifications-select"
/>
{isMultipleNotificationsEnabled && (
<Typography.Paragraph className="multiple-notifications-select-description">
<Typography.Text className="multiple-notifications-select-description">
{groupByDescription}
</Typography.Paragraph>
</Typography.Text>
)}
</div>
);

View File

@@ -1,4 +1,5 @@
import { Input, Tooltip, Typography } from 'antd';
import { Input, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { Info } from 'lucide-react';
import { useCreateAlertState } from '../context';

View File

@@ -1,4 +1,5 @@
import { Input, Select, Typography } from 'antd';
import { Input, Select } from 'antd';
import { Typography } from '@signozhq/ui';
import { useCreateAlertState } from '../context';
import {

View File

@@ -177,11 +177,7 @@ export default function CustomDomainSettings(): JSX.Element {
if (isLoadingHosts) {
return (
<div className="custom-domain-card custom-domain-card--loading">
<Skeleton
active
title={{ width: '40%' }}
paragraph={{ rows: 1, width: '60%' }}
/>
<Skeleton active paragraph={{ rows: 1, width: '60%' }} />
</div>
);
}

View File

@@ -4,16 +4,8 @@ import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Card,
Input,
Modal,
Popover,
Tag,
Tooltip,
Typography,
} from 'antd';
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';

View File

@@ -7,7 +7,8 @@ import {
useState,
} from 'react';
import { Color } from '@signozhq/design-tokens';
import { Select, Typography } from 'antd';
import { Select } from 'antd';
import { Typography } from '@signozhq/ui';
import CustomSelect from 'components/NewSelect/CustomSelect';
import TextToolTip from 'components/TextToolTip';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
@@ -213,7 +214,7 @@ function DynamicVariable({
</div>
{errorAttributeKeyMessage && (
<div>
<Typography.Text type="warning">
<Typography.Text color="warning">
{errorAttributeKeyMessage}
</Typography.Text>
</div>

View File

@@ -5,7 +5,8 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { orange } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Input, Select, Switch, Tag, Typography } from 'antd';
import { Button, Collapse, Input, Select, Switch, Tag } from 'antd';
import { Typography } from '@signozhq/ui';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import cx from 'classnames';
import Editor from 'components/Editor';
@@ -485,7 +486,7 @@ function VariableItem({
}}
/>
<div>
<Typography.Text type="warning">{errorNameMessage}</Typography.Text>
<Typography.Text color="warning">{errorNameMessage}</Typography.Text>
</div>
</div>
</VariableItemRow>

View File

@@ -11,7 +11,8 @@ import {
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Modal, Row, RowProps, Space, Table, Typography } from 'antd';
import { Button, Modal, Row, RowProps, Space, Table } from 'antd';
import { Typography } from '@signozhq/ui';
import { VariablesSettingsTabHandle } from 'container/DashboardContainer/DashboardDescription/types';
import { convertVariablesToDbFormat } from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels';

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Col, Input, Radio, Select, Space, Typography } from 'antd';
import { Col, Input, Radio, Select, Space } from 'antd';
import { Typography } from '@signozhq/ui';
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddTags';
import { useDashboardCursorSyncMode } from 'hooks/dashboard/useDashboardCursorSyncMode';
import { useSyncTooltipFilterMode } from 'hooks/dashboard/useSyncTooltipFilterMode';

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { Checkbox, toast } from '@signozhq/ui';
import { Button, Select, Typography } from 'antd';
import { Button, Select } from 'antd';
import { Typography } from '@signozhq/ui';
import createPublicDashboardAPI from 'api/dashboard/public/createPublicDashboard';
import revokePublicDashboardAccessAPI from 'api/dashboard/public/revokePublicDashboardAccess';
import updatePublicDashboardAPI from 'api/dashboard/public/updatePublicDashboard';

View File

@@ -1,7 +1,8 @@
import { memo, useMemo } from 'react';
import { orange } from '@ant-design/colors';
import { WarningOutlined } from '@ant-design/icons';
import { Popover, Tooltip, Typography } from 'antd';
import { Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui';
import { CustomMultiSelect, CustomSelect } from 'components/NewSelect';
import { OptionData } from 'components/NewSelect/types';
import { popupContainer } from 'utils/selectPopupContainer';

Some files were not shown because too many files have changed in this diff Show More