mirror of
https://github.com/SigNoz/signoz.git
synced 2026-11-04 07:20:43 +00:00
Compare commits
2 Commits
nv/exec-st
...
ns/trace-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d47be641e3 | ||
|
|
65084ba064 |
@@ -164,12 +164,12 @@ export const TILE_DRAWINGS = {
|
||||
<path d="M4 20 C 20 20, 22 12, 32 12 S 44 4, 60 4" />
|
||||
</TileSvg>
|
||||
),
|
||||
barsSideBySide: (
|
||||
barsOverlap: (
|
||||
<TileSvg>
|
||||
<rect x={10} y={10} width={7} height={12} {...FILLED} opacity={0.5} />
|
||||
<rect x={18} y={4} width={7} height={18} {...FILLED} />
|
||||
<rect x={36} y={12} width={7} height={10} {...FILLED} opacity={0.5} />
|
||||
<rect x={44} y={7} width={7} height={15} {...FILLED} />
|
||||
<rect x={14} y={6} width={11} height={16} {...FILLED} opacity={0.5} />
|
||||
<rect x={14} y={12} width={11} height={10} {...FILLED} />
|
||||
<rect x={38} y={10} width={11} height={12} {...FILLED} opacity={0.5} />
|
||||
<rect x={38} y={14} width={11} height={8} {...FILLED} />
|
||||
</TileSvg>
|
||||
),
|
||||
barsStacked: (
|
||||
|
||||
@@ -56,7 +56,7 @@ function VisualizationSection({
|
||||
value?.timePreference ?? DashboardtypesTimePreferenceDTO.global_time;
|
||||
const barLayout = value?.stackedBarChart
|
||||
? BarLayout.STACKED
|
||||
: BarLayout.SIDE_BY_SIDE;
|
||||
: BarLayout.OVERLAP;
|
||||
const stack = value?.stack ?? defaultValue?.stack;
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,15 +4,15 @@ import type { ConfigTileItem } from '../../controls/ConfigTiles/ConfigTiles';
|
||||
import { TILE_DRAWINGS } from '../../controls/drawings/tileDrawings';
|
||||
|
||||
export enum BarLayout {
|
||||
SIDE_BY_SIDE = 'side',
|
||||
OVERLAP = 'overlap',
|
||||
STACKED = 'stacked',
|
||||
}
|
||||
|
||||
export const BAR_LAYOUT_OPTIONS: ConfigTileItem<BarLayout>[] = [
|
||||
{
|
||||
value: BarLayout.SIDE_BY_SIDE,
|
||||
label: 'Side by side',
|
||||
drawing: TILE_DRAWINGS.barsSideBySide,
|
||||
value: BarLayout.OVERLAP,
|
||||
label: 'Overlap',
|
||||
drawing: TILE_DRAWINGS.barsOverlap,
|
||||
},
|
||||
{
|
||||
value: BarLayout.STACKED,
|
||||
@@ -22,8 +22,7 @@ export const BAR_LAYOUT_OPTIONS: ConfigTileItem<BarLayout>[] = [
|
||||
];
|
||||
|
||||
export const BAR_LAYOUT_HELP: Record<BarLayout, string> = {
|
||||
[BarLayout.SIDE_BY_SIDE]:
|
||||
'Each series gets its own bar, side by side, for easy comparison.',
|
||||
[BarLayout.OVERLAP]: 'Each series is drawn from zero and may hide others.',
|
||||
[BarLayout.STACKED]:
|
||||
'Bars sit on top of each other, so the height shows the total.',
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ func (b *traceQueryStatementBuilder) Build(
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
|
||||
isSelectFieldsEmpty := false
|
||||
if requestType == qbtypes.RequestTypeRaw {
|
||||
if requestType == qbtypes.RequestTypeRaw || requestType == qbtypes.RequestTypeTrace {
|
||||
isSelectFieldsEmpty = len(query.SelectFields) == 0
|
||||
// we are expanding here to ensure that all the conflicts are taken care in adjustKeys
|
||||
// i.e if there is a conflict we strip away context of the key in adjustKeys
|
||||
@@ -180,7 +180,7 @@ func (b *traceQueryStatementBuilder) Build(
|
||||
case qbtypes.RequestTypeScalar:
|
||||
return b.buildScalarQuery(ctx, orgID, q, query, start, end, keys, variables, false, false)
|
||||
case qbtypes.RequestTypeTrace:
|
||||
return b.buildTraceQuery(ctx, orgID, q, query, start, end, keys, variables)
|
||||
return b.buildTraceQuery(ctx, orgID, q, query, start, end, keys, variables, isSelectFieldsEmpty)
|
||||
}
|
||||
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
@@ -446,6 +446,7 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
|
||||
start, end uint64,
|
||||
keys map[string][]*telemetrytypes.TelemetryFieldKey,
|
||||
variables map[string]qbtypes.VariableItem,
|
||||
isSelectFieldsEmpty bool,
|
||||
) (*qbtypes.Statement, error) {
|
||||
|
||||
startBucket := start/querybuilder.NsToSeconds - querybuilder.BucketAdjustment
|
||||
@@ -483,6 +484,25 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
|
||||
// Build the inner subquery for root spans
|
||||
innerSB := sqlbuilder.NewSelectBuilder()
|
||||
innerSB.Select("trace_id", "duration_nano", sqlbuilder.Escape("resource_string_service$$name as `service.name`"), "name")
|
||||
|
||||
var rootFieldAliases []string
|
||||
info := querybuilder.NewQueryInfo(ctx, orgID, b.fl, telemetrytypes.SignalTraces, nil, start, end)
|
||||
for i, field := range query.SelectFields {
|
||||
expr, err := querybuilder.ResolveColumn(ctx, info, b.storage, &field, telemetrytypes.FieldDataTypeUnspecified, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alias := selectColumnAlias(i, field.Name)
|
||||
innerSB.SelectMore(sqlbuilder.Escape(fmt.Sprintf("%s AS %s", expr, alias)))
|
||||
rootFieldAliases = append(rootFieldAliases, alias)
|
||||
}
|
||||
if isSelectFieldsEmpty {
|
||||
for _, col := range tracestelemetryschema.ContextualSpanColumns {
|
||||
innerSB.SelectMore(col)
|
||||
rootFieldAliases = append(rootFieldAliases, col)
|
||||
}
|
||||
}
|
||||
|
||||
innerSB.From(fmt.Sprintf("%s.%s", tracestelemetryschema.DBName, tracestelemetryschema.SpanIndexV3TableName))
|
||||
innerSB.Where("parent_span_id = ''")
|
||||
|
||||
@@ -512,25 +532,23 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
|
||||
mainSB.Select(
|
||||
"__toe_duration_sorted.`service.name` AS `service.name`",
|
||||
"__toe_duration_sorted.name AS `name`",
|
||||
"count() AS span_count",
|
||||
"__toe_span_count.span_count AS span_count",
|
||||
"__toe_duration_sorted.duration_nano AS `duration_nano`",
|
||||
"__toe_duration_sorted.trace_id AS `trace_id`",
|
||||
)
|
||||
for _, alias := range rootFieldAliases {
|
||||
mainSB.SelectMore(sqlbuilder.Escape(fmt.Sprintf("__toe_duration_sorted.%s AS %s", alias, alias)))
|
||||
}
|
||||
|
||||
// Join the distributed table with the inner subquery
|
||||
mainSB.SQL("FROM __toe")
|
||||
mainSB.SQL("INNER JOIN __toe_duration_sorted")
|
||||
mainSB.SQL("ON __toe.trace_id = __toe_duration_sorted.trace_id")
|
||||
|
||||
// Group by trace-level fields
|
||||
mainSB.GroupBy("trace_id", "duration_nano", "name", "`service.name`")
|
||||
// spans are counted before the join so the root span fields are not copied onto every span
|
||||
mainSB.SQL("FROM __toe_duration_sorted")
|
||||
mainSB.SQL("INNER JOIN (SELECT trace_id, count() AS span_count FROM __toe GROUP BY trace_id) AS __toe_span_count")
|
||||
mainSB.SQL("ON __toe_span_count.trace_id = __toe_duration_sorted.trace_id")
|
||||
|
||||
// order by duration only supported for now
|
||||
mainSB.OrderBy("duration_nano DESC")
|
||||
|
||||
// Limit by trace_id to ensure one row per trace
|
||||
mainSB.SQL("LIMIT 1 BY trace_id")
|
||||
|
||||
if query.Limit > 0 {
|
||||
mainSB.Limit(query.Limit)
|
||||
} else {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -442,51 +442,3 @@ def test_builder_refreshing_a_sliding_time_range(
|
||||
assert uncached.status_code == HTTPStatus.OK, uncached.text
|
||||
|
||||
assert_results_equal(from_cache.json(), uncached.json(), "A", "the twentieth refresh")
|
||||
|
||||
|
||||
def test_builder_rows_scanned_when_refreshing(
|
||||
signoz: types.SigNoz,
|
||||
create_user_admin: None, # pylint: disable=unused-argument
|
||||
get_token: Callable[[str, str], str],
|
||||
insert_metrics: Callable[[list[Metrics]], None],
|
||||
) -> None:
|
||||
metric_name = f"cache_rows_scanned_{uuid4().hex[:8]}"
|
||||
|
||||
# 90 minutes back so even the last refresh closes clear of the flux
|
||||
# interval, which holds recent data out of the cache
|
||||
start_time = datetime.fromtimestamp(int((datetime.now(tz=UTC) - timedelta(minutes=90)).timestamp()) // 60 * 60, tz=UTC)
|
||||
start_time_ms = int(start_time.timestamp() * 1000)
|
||||
|
||||
query = [build_builder_query("A", metric_name, "max", "max")]
|
||||
|
||||
insert_metrics(
|
||||
[
|
||||
Metrics(
|
||||
metric_name=metric_name,
|
||||
labels={"service": "api"},
|
||||
timestamp=start_time + timedelta(minutes=minute),
|
||||
value=minute,
|
||||
type_="Gauge",
|
||||
is_monotonic=False,
|
||||
)
|
||||
for minute in range(70)
|
||||
]
|
||||
)
|
||||
|
||||
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
|
||||
|
||||
# a dashboard left open on a one hour range, re-running a minute later each
|
||||
# time. rowsScanned is what answering the request cost, so a refresh served
|
||||
# mostly out of the cache cannot report more than reading its range afresh
|
||||
for refresh in range(10):
|
||||
refresh_start_ms = start_time_ms + refresh * MINUTE_MS
|
||||
from_cache = make_query_request(signoz, token, refresh_start_ms, refresh_start_ms + 60 * MINUTE_MS, query, no_cache=False)
|
||||
assert from_cache.status_code == HTTPStatus.OK, from_cache.text
|
||||
|
||||
uncached = make_query_request(signoz, token, refresh_start_ms, refresh_start_ms + 60 * MINUTE_MS, query, no_cache=True)
|
||||
assert uncached.status_code == HTTPStatus.OK, uncached.text
|
||||
|
||||
rows_scanned_from_cache = from_cache.json()["data"]["meta"]["rowsScanned"]
|
||||
rows_scanned_uncached = uncached.json()["data"]["meta"]["rowsScanned"]
|
||||
assert rows_scanned_uncached > 0, f"refresh {refresh}"
|
||||
assert rows_scanned_from_cache <= rows_scanned_uncached, f"refresh {refresh}: {rows_scanned_from_cache} rows scanned when served from the cache, {rows_scanned_uncached} when read afresh"
|
||||
|
||||
Reference in New Issue
Block a user