/* * Copyright 2015-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.glowroot.central.repo; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import java.util.regex.Pattern; import java.util.stream.Collectors; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.google.common.base.Strings; import com.google.common.collect.Ordering; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import org.checkerframework.checker.nullness.qual.Nullable; import org.immutables.value.Value; import org.glowroot.central.util.CassandraWriteMetrics; import org.glowroot.central.util.Messages; import org.glowroot.central.util.MoreFutures; import org.glowroot.central.util.Session; import org.glowroot.common.Constants; import org.glowroot.common.live.ImmutableEntries; import org.glowroot.common.live.ImmutableEntriesAndQueries; import org.glowroot.common.live.ImmutableQueries; import org.glowroot.common.live.ImmutableTracePoint; import org.glowroot.common.live.LiveTraceRepository.Entries; import org.glowroot.common.live.LiveTraceRepository.EntriesAndQueries; import org.glowroot.common.live.LiveTraceRepository.Existence; import org.glowroot.common.live.LiveTraceRepository.Queries; import org.glowroot.common.live.LiveTraceRepository.TracePoint; import org.glowroot.common.live.LiveTraceRepository.TracePointFilter; import org.glowroot.common.model.Result; import org.glowroot.common.util.CaptureTimes; import org.glowroot.common.util.Clock; import org.glowroot.common.util.NotAvailableAware; import org.glowroot.common.util.OnlyUsedByTests; import org.glowroot.common2.repo.ImmutableErrorMessageCount; import org.glowroot.common2.repo.ImmutableErrorMessagePoint; import org.glowroot.common2.repo.ImmutableErrorMessageResult; import org.glowroot.common2.repo.ImmutableHeaderPlus; import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate; import org.glowroot.wire.api.model.ProfileOuterClass.Profile; import org.glowroot.wire.api.model.Proto; import org.glowroot.wire.api.model.Proto.OptionalInt64; import org.glowroot.wire.api.model.Proto.StackTraceElement; import org.glowroot.wire.api.model.TraceOuterClass.Trace; import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MINUTES; public class TraceDaoImpl implements TraceDao { @SuppressWarnings("deprecation") private static final HashFunction SHA_1 = Hashing.sha1(); private final Session session; private final TransactionTypeDao transactionTypeDao; private final FullQueryTextDao fullQueryTextDao; private final TraceAttributeNameDao traceAttributeNameDao; private final ConfigRepositoryImpl configRepository; private final Clock clock; private final boolean cassandra2x; private final PreparedStatement insertOverallSlowCount; private final PreparedStatement insertOverallSlowCountPartial; private final PreparedStatement insertTransactionSlowCount; private final PreparedStatement insertTransactionSlowCountPartial; private final PreparedStatement insertOverallSlowPoint; private final PreparedStatement insertOverallSlowPointPartial; private final PreparedStatement insertTransactionSlowPoint; private final PreparedStatement insertTransactionSlowPointPartial; private final PreparedStatement insertOverallErrorCount; private final PreparedStatement insertTransactionErrorCount; private final PreparedStatement insertOverallErrorPoint; private final PreparedStatement insertTransactionErrorPoint; private final PreparedStatement insertOverallErrorMessage; private final PreparedStatement insertTransactionErrorMessage; private final PreparedStatement insertHeaderV2; private final PreparedStatement insertEntryV2; private final PreparedStatement insertQueryV2; private final PreparedStatement insertSharedQueryTextV2; private final PreparedStatement insertMainThreadProfileV2; private final PreparedStatement insertAuxThreadProfileV2; private final PreparedStatement readOverallSlowCount; private final PreparedStatement readOverallSlowCountPartial; private final PreparedStatement readTransactionSlowCount; private final PreparedStatement readTransactionSlowCountPartial; private final PreparedStatement readOverallSlowPoint; private final PreparedStatement readOverallSlowPointPartial; private final PreparedStatement readTransactionSlowPoint; private final PreparedStatement readTransactionSlowPointPartial; private final PreparedStatement readOverallErrorCount; private final PreparedStatement readTransactionErrorCount; private final PreparedStatement readOverallErrorPoint; private final PreparedStatement readTransactionErrorPoint; private final PreparedStatement readOverallErrorMessage; private final PreparedStatement readTransactionErrorMessage; private final PreparedStatement readHeaderV1; private final PreparedStatement readEntriesV1; private final PreparedStatement readSharedQueryTextsV1; private final PreparedStatement readMainThreadProfileV1; private final PreparedStatement readAuxThreadProfileV1; private final PreparedStatement readHeaderV2; private final PreparedStatement readEntriesV2; private final PreparedStatement readQueriesV2; private final PreparedStatement readSharedQueryTextsV2; private final PreparedStatement readMainThreadProfileV2; private final PreparedStatement readAuxThreadProfileV2; private final PreparedStatement deleteOverallSlowCountPartial; private final PreparedStatement deleteTransactionSlowCountPartial; private final PreparedStatement deleteOverallSlowPointPartial; private final PreparedStatement deleteTransactionSlowPointPartial; TraceDaoImpl(Session session, TransactionTypeDao transactionTypeDao, FullQueryTextDao fullQueryTextDao, TraceAttributeNameDao traceAttributeNameDao, ConfigRepositoryImpl configRepository, Clock clock) throws Exception { this.session = session; this.transactionTypeDao = transactionTypeDao; this.fullQueryTextDao = fullQueryTextDao; this.traceAttributeNameDao = traceAttributeNameDao; this.configRepository = configRepository; this.clock = clock; ResultSet results = session.read("select release_version from system.local where key = 'local'"); Row row = checkNotNull(results.one()); String cassandraVersion = checkNotNull(row.getString(0)); cassandra2x = cassandraVersion.startsWith("2."); int expirationHours = configRepository.getCentralStorageConfig().traceExpirationHours(); // agent_rollup/capture_time is not necessarily unique // using a counter would be nice since only need sum over capture_time range // but counter has no TTL, see https://issues.apache.org/jira/browse/CASSANDRA-2103 // so adding trace_id to provide uniqueness session.createTableWithTWCS("create table if not exists trace_tt_slow_count (agent_rollup" + " varchar, transaction_type varchar, capture_time timestamp, agent_id varchar," + " trace_id varchar, primary key ((agent_rollup, transaction_type), capture_time," + " agent_id, trace_id))", expirationHours); // "capture_time" column now should be "capture_time_partial_rollup" (since 0.13.1) // (and "real_capture_time" column now should be "capture_time") session.createTableWithTWCS("create table if not exists trace_tt_slow_count_partial" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, real_capture_time timestamp, primary key" + " ((agent_rollup, transaction_type), capture_time, agent_id, trace_id))", expirationHours, false, true); session.createTableWithTWCS("create table if not exists trace_tn_slow_count (agent_rollup" + " varchar, transaction_type varchar, transaction_name varchar, capture_time" + " timestamp, agent_id varchar, trace_id varchar, primary key ((agent_rollup," + " transaction_type, transaction_name), capture_time, agent_id, trace_id))", expirationHours); // "capture_time" column now should be "capture_time_partial_rollup" (since 0.13.1) // (and "real_capture_time" column now should be "capture_time") session.createTableWithTWCS("create table if not exists trace_tn_slow_count_partial" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar, real_capture_time" + " timestamp, primary key ((agent_rollup, transaction_type, transaction_name)," + " capture_time, agent_id, trace_id))", expirationHours, false, true); session.createTableWithTWCS("create table if not exists trace_tt_slow_point (agent_rollup" + " varchar, transaction_type varchar, capture_time timestamp, agent_id varchar," + " trace_id varchar, duration_nanos bigint, error boolean, headline varchar, user" + " varchar, attributes blob, primary key ((agent_rollup, transaction_type)," + " capture_time, agent_id, trace_id))", expirationHours); // "capture_time" column now should be "capture_time_partial_rollup" (since 0.13.1) // (and "real_capture_time" column now should be "capture_time") session.createTableWithTWCS("create table if not exists trace_tt_slow_point_partial" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, real_capture_time timestamp, duration_nanos" + " bigint, error boolean, headline varchar, user varchar, attributes blob, primary" + " key ((agent_rollup, transaction_type), capture_time, agent_id, trace_id))", expirationHours, false, true); session.createTableWithTWCS("create table if not exists trace_tn_slow_point (agent_rollup" + " varchar, transaction_type varchar, transaction_name varchar, capture_time" + " timestamp, agent_id varchar, trace_id varchar, duration_nanos bigint, error" + " boolean, headline varchar, user varchar, attributes blob, primary key" + " ((agent_rollup, transaction_type, transaction_name), capture_time, agent_id," + " trace_id))", expirationHours); // "capture_time" column now should be "capture_time_partial_rollup" (since 0.13.1) // (and "real_capture_time" column now should be "capture_time") session.createTableWithTWCS("create table if not exists trace_tn_slow_point_partial" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar, real_capture_time" + " timestamp, duration_nanos bigint, error boolean, headline varchar, user" + " varchar, attributes blob, primary key ((agent_rollup, transaction_type," + " transaction_name), capture_time, agent_id, trace_id))", expirationHours, false, true); session.createTableWithTWCS("create table if not exists trace_tt_error_count (agent_rollup" + " varchar, transaction_type varchar, capture_time timestamp, agent_id varchar," + " trace_id varchar, primary key ((agent_rollup, transaction_type), capture_time," + " agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_tn_error_count (agent_rollup" + " varchar, transaction_type varchar, transaction_name varchar, capture_time" + " timestamp, agent_id varchar, trace_id varchar, primary key ((agent_rollup," + " transaction_type, transaction_name), capture_time, agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_tt_error_point (agent_rollup" + " varchar, transaction_type varchar, capture_time timestamp, agent_id varchar," + " trace_id varchar, duration_nanos bigint, error_message varchar, headline" + " varchar, user varchar, attributes blob, primary key ((agent_rollup," + " transaction_type), capture_time, agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_tn_error_point (agent_rollup" + " varchar, transaction_type varchar, transaction_name varchar, capture_time" + " timestamp, agent_id varchar, trace_id varchar, duration_nanos bigint," + " error_message varchar, headline varchar, user varchar, attributes blob, primary" + " key ((agent_rollup, transaction_type, transaction_name), capture_time," + " agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_tt_error_message" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, error_message varchar, primary key" + " ((agent_rollup, transaction_type), capture_time, agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_tn_error_message" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar, error_message" + " varchar, primary key ((agent_rollup, transaction_type, transaction_name)," + " capture_time, agent_id, trace_id))", expirationHours); // ===== trace components v1 ===== session.createTableWithTWCS("create table if not exists trace_header (agent_id varchar," + " trace_id varchar, header blob, primary key (agent_id, trace_id))", expirationHours); // index_ is just to provide uniqueness session.createTableWithTWCS("create table if not exists trace_entry (agent_id varchar," + " trace_id varchar, index_ int, depth int, start_offset_nanos bigint," + " duration_nanos bigint, active boolean, message varchar, shared_query_text_index" + " int, query_message_prefix varchar, query_message_suffix varchar, detail blob," + " location_stack_trace blob, error blob, primary key (agent_id, trace_id," + " index_))", expirationHours); // index_ is just to provide uniqueness session.createTableWithTWCS("create table if not exists trace_shared_query_text (agent_id" + " varchar, trace_id varchar, index_ int, truncated_text varchar," + " truncated_end_text varchar, full_text_sha1 varchar, primary key (agent_id," + " trace_id, index_))", expirationHours); session.createTableWithTWCS("create table if not exists trace_main_thread_profile (agent_id" + " varchar, trace_id varchar, profile blob, primary key (agent_id, trace_id))", expirationHours); session.createTableWithTWCS("create table if not exists trace_aux_thread_profile (agent_id" + " varchar, trace_id varchar, profile blob, primary key (agent_id, trace_id))", expirationHours); // ===== trace components v2 ===== session.createTableWithTWCS("create table if not exists trace_header_v2 (agent_id varchar," + " trace_id varchar, header blob, primary key ((agent_id, trace_id)))", expirationHours); // index_ is used to provide uniqueness and ordering session.createTableWithTWCS("create table if not exists trace_entry_v2 (agent_id varchar," + " trace_id varchar, index_ int, depth int, start_offset_nanos bigint," + " duration_nanos bigint, active boolean, message varchar, shared_query_text_index" + " int, query_message_prefix varchar, query_message_suffix varchar, detail blob," + " location_stack_trace blob, error blob, primary key ((agent_id, trace_id)," + " index_))", expirationHours); session.createTableWithTWCS("create table if not exists trace_query_v2 (agent_id varchar," + " trace_id varchar, type varchar, shared_query_text_index int," + " total_duration_nanos double, execution_count bigint, total_rows bigint," + " active boolean, primary key ((agent_id, trace_id), type," + " shared_query_text_index))", expirationHours); // index_ is used to provide uniqueness and ordering session.createTableWithTWCS("create table if not exists trace_shared_query_text_v2" + " (agent_id varchar, trace_id varchar, index_ int, truncated_text varchar," + " truncated_end_text varchar, full_text_sha1 varchar, primary key ((agent_id," + " trace_id), index_))", expirationHours); session.createTableWithTWCS("create table if not exists trace_main_thread_profile_v2" + " (agent_id varchar, trace_id varchar, profile blob, primary key ((agent_id," + " trace_id)))", expirationHours); session.createTableWithTWCS("create table if not exists trace_aux_thread_profile_v2" + " (agent_id varchar, trace_id varchar, profile blob, primary key ((agent_id," + " trace_id)))", expirationHours); insertOverallSlowCount = session.prepare("insert into trace_tt_slow_count (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id) values (?, ?, ?, ?, ?)" + " using ttl ?"); insertOverallSlowCountPartial = session.prepare("insert into trace_tt_slow_count_partial" + " (agent_rollup, transaction_type, capture_time, agent_id, trace_id," + " real_capture_time) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionSlowCount = session.prepare("insert into trace_tn_slow_count" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionSlowCountPartial = session.prepare("insert into" + " trace_tn_slow_count_partial (agent_rollup, transaction_type, transaction_name," + " capture_time, agent_id, trace_id, real_capture_time) values (?, ?, ?, ?, ?, ?," + " ?) using ttl ?"); insertOverallSlowPoint = session.prepare("insert into trace_tt_slow_point (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id, duration_nanos, error," + " headline, user, attributes) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallSlowPointPartial = session.prepare("insert into trace_tt_slow_point_partial" + " (agent_rollup, transaction_type, capture_time, agent_id, trace_id," + " real_capture_time, duration_nanos, error, headline, user, attributes) values" + " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionSlowPoint = session.prepare("insert into trace_tn_slow_point" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, duration_nanos, error, headline, user, attributes) values (?, ?, ?," + " ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionSlowPointPartial = session.prepare("insert into" + " trace_tn_slow_point_partial (agent_rollup, transaction_type, transaction_name," + " capture_time, agent_id, trace_id, real_capture_time, duration_nanos, error," + " headline, user, attributes) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using" + " ttl ?"); insertOverallErrorCount = session.prepare("insert into trace_tt_error_count (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id) values (?, ?, ?, ?, ?)" + " using ttl ?"); insertTransactionErrorCount = session.prepare("insert into trace_tn_error_count" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallErrorPoint = session.prepare("insert into trace_tt_error_point (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id, duration_nanos," + " error_message, headline, user, attributes) values (?, ?, ?, ?, ?, ?, ?, ?, ?," + " ?) using ttl ?"); insertTransactionErrorPoint = session.prepare("insert into trace_tn_error_point" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, duration_nanos, error_message, headline, user, attributes) values (?," + " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallErrorMessage = session.prepare("insert into trace_tt_error_message" + " (agent_rollup, transaction_type, capture_time, agent_id, trace_id," + " error_message) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionErrorMessage = session.prepare("insert into trace_tn_error_message" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, error_message) values (?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertHeaderV2 = session.prepare("insert into trace_header_v2 (agent_id, trace_id, header)" + " values (?, ?, ?) using ttl ?"); insertEntryV2 = session.prepare("insert into trace_entry_v2 (agent_id, trace_id, index_," + " depth, start_offset_nanos, duration_nanos, active, message," + " shared_query_text_index, query_message_prefix, query_message_suffix, detail," + " location_stack_trace, error) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + " using ttl ?"); insertQueryV2 = session.prepare("insert into trace_query_v2 (agent_id, trace_id, type," + " shared_query_text_index, total_duration_nanos, execution_count, total_rows," + " active) values (?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertSharedQueryTextV2 = session.prepare("insert into trace_shared_query_text_v2" + " (agent_id, trace_id, index_, truncated_text, truncated_end_text," + " full_text_sha1) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertMainThreadProfileV2 = session.prepare("insert into trace_main_thread_profile_v2" + " (agent_id, trace_id, profile) values (?, ?, ?) using ttl ?"); insertAuxThreadProfileV2 = session.prepare("insert into trace_aux_thread_profile_v2" + " (agent_id, trace_id, profile) values (?, ?, ?) using ttl ?"); readOverallSlowCount = session.prepare("select count(*) from trace_tt_slow_count where" + " agent_rollup = ? and transaction_type = ? and capture_time > ? and capture_time" + " <= ?"); if (cassandra2x) { // Cassandra 2.x doesn't support predicates on non-primary-key columns readOverallSlowCountPartial = session.prepare("select count(*) from" + " trace_tt_slow_count_partial where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ?"); } else { readOverallSlowCountPartial = session.prepare("select count(*) from" + " trace_tt_slow_count_partial where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ? and real_capture_time > ? and" + " real_capture_time <= ? allow filtering"); } readTransactionSlowCount = session.prepare("select count(*) from trace_tn_slow_count where" + " agent_rollup = ? and transaction_type = ? and transaction_name = ? and" + " capture_time > ? and capture_time <= ?"); if (cassandra2x) { // Cassandra 2.x doesn't support predicates on non-primary-key columns readTransactionSlowCountPartial = session.prepare("select count(*) from" + " trace_tn_slow_count_partial where agent_rollup = ? and transaction_type = ?" + " and transaction_name = ? and capture_time > ? and capture_time <= ?"); } else { readTransactionSlowCountPartial = session.prepare("select count(*) from" + " trace_tn_slow_count_partial where agent_rollup = ? and transaction_type = ?" + " and transaction_name = ? and capture_time > ? and capture_time <= ? and" + " real_capture_time > ? and real_capture_time <= ? allow filtering"); } readOverallSlowPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, error, headline, user, attributes from trace_tt_slow_point" + " where agent_rollup = ? and transaction_type = ? and capture_time > ? and" + " capture_time <= ?"); if (cassandra2x) { // Cassandra 2.x doesn't support predicates on non-primary-key columns readOverallSlowPointPartial = session.prepare("select agent_id, trace_id, capture_time," + " real_capture_time, duration_nanos, error, headline, user, attributes from" + " trace_tt_slow_point_partial where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ?"); } else { readOverallSlowPointPartial = session.prepare("select agent_id, trace_id, capture_time," + " real_capture_time, duration_nanos, error, headline, user, attributes from" + " trace_tt_slow_point_partial where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ? and real_capture_time > ? and" + " real_capture_time <= ? allow filtering"); } readTransactionSlowPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, error, headline, user, attributes from trace_tn_slow_point" + " where agent_rollup = ? and transaction_type = ? and transaction_name = ? and" + " capture_time > ? and capture_time <= ?"); if (cassandra2x) { // Cassandra 2.x doesn't support predicates on non-primary-key columns readTransactionSlowPointPartial = session.prepare("select agent_id, trace_id," + " capture_time, real_capture_time, duration_nanos, error, headline, user," + " attributes from trace_tn_slow_point_partial where agent_rollup = ? and" + " transaction_type = ? and transaction_name = ? and capture_time > ? and" + " capture_time <= ?"); } else { readTransactionSlowPointPartial = session.prepare("select agent_id, trace_id," + " capture_time, real_capture_time, duration_nanos, error, headline, user," + " attributes from trace_tn_slow_point_partial where agent_rollup = ? and" + " transaction_type = ? and transaction_name = ? and capture_time > ? and" + " capture_time <= ? and real_capture_time > ? and real_capture_time <= ?" + " allow filtering"); } readOverallErrorCount = session.prepare("select count(*) from trace_tt_error_count where" + " agent_rollup = ? and transaction_type = ? and capture_time > ? and" + " capture_time <= ?"); readTransactionErrorCount = session.prepare("select count(*) from trace_tn_error_count" + " where agent_rollup = ? and transaction_type = ? and transaction_name = ?" + " and capture_time > ? and capture_time <= ?"); readOverallErrorPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, error_message, headline, user, attributes from" + " trace_tt_error_point where agent_rollup = ? and transaction_type = ? and" + " capture_time > ? and capture_time <= ?"); readTransactionErrorPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, error_message, headline, user, attributes from" + " trace_tn_error_point where agent_rollup = ? and transaction_type = ? and" + " transaction_name = ? and capture_time > ? and capture_time <= ?"); readOverallErrorMessage = session.prepare("select capture_time, error_message from" + " trace_tt_error_message where agent_rollup = ? and transaction_type = ? and" + " capture_time > ? and capture_time <= ?"); readTransactionErrorMessage = session.prepare("select capture_time, error_message from" + " trace_tn_error_message where agent_rollup = ? and transaction_type = ? and" + " transaction_name = ? and capture_time > ? and capture_time <= ?"); readHeaderV1 = session .prepare("select header from trace_header where agent_id = ? and trace_id = ?"); readEntriesV1 = session.prepare("select depth, start_offset_nanos, duration_nanos, active," + " message, shared_query_text_index, query_message_prefix, query_message_suffix," + " detail, location_stack_trace, error from trace_entry where agent_id = ? and" + " trace_id = ?"); readSharedQueryTextsV1 = session.prepare("select truncated_text, truncated_end_text," + " full_text_sha1 from trace_shared_query_text where agent_id = ? and trace_id" + " = ?"); readMainThreadProfileV1 = session.prepare("select profile from trace_main_thread_profile" + " where agent_id = ? and trace_id = ?"); readAuxThreadProfileV1 = session.prepare("select profile from trace_aux_thread_profile" + " where agent_id = ? and trace_id = ?"); readHeaderV2 = session .prepare("select header from trace_header_v2 where agent_id = ? and trace_id = ?"); readEntriesV2 = session.prepare("select depth, start_offset_nanos, duration_nanos, active," + " message, shared_query_text_index, query_message_prefix, query_message_suffix," + " detail, location_stack_trace, error from trace_entry_v2 where agent_id = ? and" + " trace_id = ?"); readQueriesV2 = session.prepare("select type, shared_query_text_index," + " total_duration_nanos, execution_count, total_rows, active from trace_query_v2" + " where agent_id = ? and trace_id = ?"); readSharedQueryTextsV2 = session.prepare("select truncated_text, truncated_end_text," + " full_text_sha1 from trace_shared_query_text_v2 where agent_id = ? and trace_id" + " = ?"); readMainThreadProfileV2 = session.prepare("select profile from trace_main_thread_profile_v2" + " where agent_id = ? and trace_id = ?"); readAuxThreadProfileV2 = session.prepare("select profile from trace_aux_thread_profile_v2" + " where agent_id = ? and trace_id = ?"); deleteOverallSlowCountPartial = session.prepare("delete from trace_tt_slow_count_partial" + " where agent_rollup = ? and transaction_type = ? and capture_time = ? and" + " agent_id = ? and trace_id = ?"); deleteTransactionSlowCountPartial = session.prepare("delete from" + " trace_tn_slow_count_partial where agent_rollup = ? and transaction_type = ? and" + " transaction_name = ? and capture_time = ? and agent_id = ? and trace_id = ?"); deleteOverallSlowPointPartial = session.prepare("delete from trace_tt_slow_point_partial" + " where agent_rollup = ? and transaction_type = ? and capture_time = ? and" + " agent_id = ? and trace_id = ?"); deleteTransactionSlowPointPartial = session.prepare("delete from" + " trace_tn_slow_point_partial where agent_rollup = ? and transaction_type = ? and" + " transaction_name = ? and capture_time = ? and agent_id = ? and trace_id = ?"); } @Override public void store(String agentId, Trace trace) throws Exception { List<String> agentRollupIds = AgentRollupIds.getAgentRollupIds(agentId); store(agentId, agentRollupIds, agentRollupIds, trace); } public void store(String agentId, List<String> agentRollupIds, List<String> agentRollupIdsForMeta, Trace trace) throws Exception { CassandraWriteMetrics cassandraWriteMetrics = session.getCassandraWriteMetrics(); cassandraWriteMetrics.setCurrTransactionType(trace.getHeader().getTransactionType()); cassandraWriteMetrics.setCurrTransactionName(trace.getHeader().getTransactionName()); cassandraWriteMetrics.setPartialTrace(trace.getHeader().getPartial()); try { storeInternal(agentId, agentRollupIds, agentRollupIdsForMeta, trace); } finally { cassandraWriteMetrics.setCurrTransactionType(null); cassandraWriteMetrics.setCurrTransactionName(null); cassandraWriteMetrics.setPartialTrace(false); } } private void storeInternal(String agentId, List<String> agentRollupIds, List<String> agentRollupIdsForMeta, Trace trace) throws Exception { String traceId = trace.getId(); Trace.Header priorHeader = trace.getUpdate() ? readHeader(agentId, traceId) : null; Trace.Header header = trace.getHeader(); if (header.getPartial()) { header = header.toBuilder() .setCaptureTimePartialRollup( getCaptureTimePartialRollup(header.getCaptureTime())) .build(); } List<Future<?>> futures = new ArrayList<>(); List<Trace.SharedQueryText> sharedQueryTexts = new ArrayList<>(); for (Trace.SharedQueryText sharedQueryText : trace.getSharedQueryTextList()) { String fullTextSha1 = sharedQueryText.getFullTextSha1(); if (fullTextSha1.isEmpty()) { String fullText = sharedQueryText.getFullText(); if (fullText.length() > 2 * Constants.TRACE_QUERY_TEXT_TRUNCATE) { // relying on agent side to rate limit (re-)sending the same full text fullTextSha1 = SHA_1.hashString(fullText, UTF_8).toString(); futures.addAll(fullQueryTextDao.store(agentRollupIds, fullTextSha1, fullText)); sharedQueryText = Trace.SharedQueryText.newBuilder() .setTruncatedText( fullText.substring(0, Constants.TRACE_QUERY_TEXT_TRUNCATE)) .setTruncatedEndText(fullText.substring( fullText.length() - Constants.TRACE_QUERY_TEXT_TRUNCATE, fullText.length())) .setFullTextSha1(fullTextSha1) .build(); } } sharedQueryTexts.add(sharedQueryText); } // wait for success before proceeding in order to ensure cannot end up with orphaned // fullTextSha1 MoreFutures.waitForAll(futures); futures.clear(); int adjustedTTL = Common.getAdjustedTTL(configRepository.getCentralStorageConfig().getTraceTTL(), header.getCaptureTime(), clock); for (String agentRollupId : agentRollupIds) { if (header.getSlow()) { BoundStatement boundStatement; if (header.getPartial()) { boundStatement = insertOverallSlowPointPartial.bind(); } else { boundStatement = insertOverallSlowPoint.bind(); } bindSlowPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true, header.getPartial(), cassandra2x); futures.add(session.writeAsync(boundStatement)); if (header.getPartial()) { boundStatement = insertTransactionSlowPointPartial.bind(); } else { boundStatement = insertTransactionSlowPoint.bind(); } bindSlowPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false, header.getPartial(), cassandra2x); futures.add(session.writeAsync(boundStatement)); if (header.getPartial()) { boundStatement = insertOverallSlowCountPartial.bind(); } else { boundStatement = insertOverallSlowCount.bind(); } bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true, header.getPartial(), cassandra2x); futures.add(session.writeAsync(boundStatement)); if (header.getPartial()) { boundStatement = insertTransactionSlowCountPartial.bind(); } else { boundStatement = insertTransactionSlowCount.bind(); } bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false, header.getPartial(), cassandra2x); futures.add(session.writeAsync(boundStatement)); if (priorHeader != null && priorHeader.getCaptureTimePartialRollup() != header .getCaptureTimePartialRollup()) { boolean useCaptureTimePartialRollup = priorHeader.getCaptureTimePartialRollup() != 0; boundStatement = deleteOverallSlowPointPartial.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, true, useCaptureTimePartialRollup); futures.add(session.writeAsync(boundStatement)); boundStatement = deleteTransactionSlowPointPartial.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, false, useCaptureTimePartialRollup); futures.add(session.writeAsync(boundStatement)); boundStatement = deleteOverallSlowCountPartial.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, true, useCaptureTimePartialRollup); futures.add(session.writeAsync(boundStatement)); boundStatement = deleteTransactionSlowCountPartial.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, false, useCaptureTimePartialRollup); futures.add(session.writeAsync(boundStatement)); } } // seems unnecessary to insert error info for partial traces // and this avoids having to clean up partial trace data when trace is complete if (header.hasError() && !header.getPartial()) { BoundStatement boundStatement = insertOverallErrorMessage.bind(); bindErrorMessage(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.writeAsync(boundStatement)); boundStatement = insertTransactionErrorMessage.bind(); bindErrorMessage(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.writeAsync(boundStatement)); boundStatement = insertOverallErrorPoint.bind(); bindErrorPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.writeAsync(boundStatement)); boundStatement = insertTransactionErrorPoint.bind(); bindErrorPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.writeAsync(boundStatement)); boundStatement = insertOverallErrorCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true, false, cassandra2x); futures.add(session.writeAsync(boundStatement)); boundStatement = insertTransactionErrorCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false, false, cassandra2x); futures.add(session.writeAsync(boundStatement)); } } for (String agentRollupIdForMeta : agentRollupIdsForMeta) { for (Trace.Attribute attributeName : header.getAttributeList()) { traceAttributeNameDao.store(agentRollupIdForMeta, header.getTransactionType(), attributeName.getName(), futures); } } BoundStatement boundStatement = insertHeaderV2.bind(); int i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setBytes(i++, ByteBuffer.wrap(header.toByteArray())); boundStatement.setInt(i++, adjustedTTL); futures.add(session.writeAsync(boundStatement)); int index = 0; for (Trace.Entry entry : trace.getEntryList()) { boundStatement = insertEntryV2.bind(); i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setInt(i++, index++); boundStatement.setInt(i++, entry.getDepth()); boundStatement.setLong(i++, entry.getStartOffsetNanos()); boundStatement.setLong(i++, entry.getDurationNanos()); boundStatement.setBool(i++, entry.getActive()); if (entry.hasQueryEntryMessage()) { boundStatement.setToNull(i++); boundStatement.setInt(i++, entry.getQueryEntryMessage().getSharedQueryTextIndex()); boundStatement.setString(i++, Strings.emptyToNull(entry.getQueryEntryMessage().getPrefix())); boundStatement.setString(i++, Strings.emptyToNull(entry.getQueryEntryMessage().getSuffix())); } else { // message is empty for trace entries added using addErrorEntry() boundStatement.setString(i++, Strings.emptyToNull(entry.getMessage())); boundStatement.setToNull(i++); boundStatement.setToNull(i++); boundStatement.setToNull(i++); } List<Trace.DetailEntry> detailEntries = entry.getDetailEntryList(); if (detailEntries.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(detailEntries)); } List<StackTraceElement> location = entry.getLocationStackTraceElementList(); if (location.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(location)); } if (entry.hasError()) { boundStatement.setBytes(i++, ByteBuffer.wrap(entry.getError().toByteArray())); } else { boundStatement.setToNull(i++); } boundStatement.setInt(i++, adjustedTTL); futures.add(session.writeAsync(boundStatement)); } for (Aggregate.Query query : trace.getQueryList()) { boundStatement = insertQueryV2.bind(); i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setString(i++, query.getType()); boundStatement.setInt(i++, query.getSharedQueryTextIndex()); boundStatement.setDouble(i++, query.getTotalDurationNanos()); boundStatement.setLong(i++, query.getExecutionCount()); if (query.hasTotalRows()) { boundStatement.setLong(i++, query.getTotalRows().getValue()); } else { boundStatement.setLong(i++, NotAvailableAware.NA); } boundStatement.setBool(i++, query.getActive()); boundStatement.setInt(i++, adjustedTTL); futures.add(session.writeAsync(boundStatement)); } index = 0; for (Trace.SharedQueryText sharedQueryText : sharedQueryTexts) { boundStatement = insertSharedQueryTextV2.bind(); i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setInt(i++, index++); String fullText = sharedQueryText.getFullText(); if (fullText.isEmpty()) { boundStatement.setString(i++, sharedQueryText.getTruncatedText()); boundStatement.setString(i++, sharedQueryText.getTruncatedEndText()); boundStatement.setString(i++, sharedQueryText.getFullTextSha1()); } else { boundStatement.setString(i++, fullText); boundStatement.setToNull(i++); boundStatement.setToNull(i++); } boundStatement.setInt(i++, adjustedTTL); futures.add(session.writeAsync(boundStatement)); } if (trace.hasMainThreadProfile()) { boundStatement = insertMainThreadProfileV2.bind(); bindThreadProfile(boundStatement, agentId, traceId, trace.getMainThreadProfile(), adjustedTTL); futures.add(session.writeAsync(boundStatement)); } if (trace.hasAuxThreadProfile()) { boundStatement = insertAuxThreadProfileV2.bind(); bindThreadProfile(boundStatement, agentId, traceId, trace.getAuxThreadProfile(), adjustedTTL); futures.add(session.writeAsync(boundStatement)); } futures.addAll( transactionTypeDao.store(agentRollupIdsForMeta, header.getTransactionType())); MoreFutures.waitForAll(futures); } @Override public long readSlowCount(String agentRollupId, TraceQuery query) throws Exception { BoundStatement boundStatement; BoundStatement boundStatementPartial; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallSlowCount.bind(); boundStatementPartial = readOverallSlowCountPartial.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); bindTraceQueryPartial(boundStatementPartial, agentRollupId, query, true, cassandra2x); } else { boundStatement = readTransactionSlowCount.bind(); boundStatementPartial = readTransactionSlowCountPartial.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); bindTraceQueryPartial(boundStatementPartial, agentRollupId, query, false, cassandra2x); } Future<ResultSet> future = session.readAsync(boundStatement); Future<ResultSet> futurePartial = session.readAsync(boundStatementPartial); return future.get().one().getLong(0) + futurePartial.get().one().getLong(0); } @Override public Result<TracePoint> readSlowPoints(String agentRollupId, TraceQuery query, TracePointFilter filter, int limit) throws Exception { BoundStatement boundStatement; BoundStatement boundStatementPartial; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallSlowPoint.bind(); boundStatementPartial = readOverallSlowPointPartial.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); bindTraceQueryPartial(boundStatementPartial, agentRollupId, query, true, cassandra2x); } else { boundStatement = readTransactionSlowPoint.bind(); boundStatementPartial = readTransactionSlowPointPartial.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); bindTraceQueryPartial(boundStatementPartial, agentRollupId, query, false, cassandra2x); } Future<ResultSet> future = session.readAsync(boundStatement); Future<ResultSet> futurePartial = session.readAsync(boundStatementPartial); List<TracePoint> completedPoints = processPoints(future.get(), filter, false, false); List<TracePoint> partialPoints = processPoints(futurePartial.get(), filter, true, false); return combine(completedPoints, partialPoints, limit); } @Override public long readErrorCount(String agentRollupId, TraceQuery query) throws Exception { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorCount.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorCount.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.read(boundStatement); return results.one().getLong(0); } @Override public Result<TracePoint> readErrorPoints(String agentRollupId, TraceQuery query, TracePointFilter filter, int limit) throws Exception { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.read(boundStatement); List<TracePoint> errorPoints = processPoints(results, filter, false, true); return createResult(errorPoints, limit); } @Override public ErrorMessageResult readErrorMessages(String agentRollupId, TraceQuery query, ErrorMessageFilter filter, long resolutionMillis, int limit) throws Exception { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.read(boundStatement); // rows are already in order by captureTime, so saving sort step by using linked hash map Map<Long, MutableLong> pointCounts = new LinkedHashMap<>(); Map<String, MutableLong> messageCounts = new HashMap<>(); for (Row row : results) { long captureTime = checkNotNull(row.getTimestamp(0)).getTime(); String errorMessage = checkNotNull(row.getString(1)); if (!matches(filter, errorMessage)) { continue; } long rollupCaptureTime = CaptureTimes.getRollup(captureTime, resolutionMillis); pointCounts.computeIfAbsent(rollupCaptureTime, k -> new MutableLong()).increment(); messageCounts.computeIfAbsent(errorMessage, k -> new MutableLong()).increment(); } // pointCounts is linked hash map and is already sorted by capture time List<ErrorMessagePoint> points = pointCounts.entrySet().stream() .map(e -> ImmutableErrorMessagePoint.of(e.getKey(), e.getValue().value)) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<ErrorMessagePoint>toList()); List<ErrorMessageCount> counts = messageCounts.entrySet().stream() .map(e1 -> ImmutableErrorMessageCount.of(e1.getKey(), e1.getValue().value)) .sorted(Comparator.comparing(ErrorMessageCount::count).reversed()) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<ErrorMessageCount>toList()); if (counts.size() > limit) { return ImmutableErrorMessageResult.builder() .addAllPoints(points) .counts(new Result<>(counts.subList(0, limit), true)) .build(); } else { return ImmutableErrorMessageResult.builder() .addAllPoints(points) .counts(new Result<>(counts, false)) .build(); } } @Override public long readErrorMessageCount(String agentRollupId, TraceQuery query, String errorMessageFilter) throws Exception { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } Pattern errorMessagePattern; if (errorMessageFilter.startsWith("/") && errorMessageFilter.endsWith("/")) { // case insensitive search must be explicit via (?i) at beginning of pattern errorMessagePattern = Pattern.compile( errorMessageFilter.substring(1, errorMessageFilter.length() - 1), Pattern.DOTALL); } else { errorMessagePattern = null; } ResultSet results = session.read(boundStatement); long count = 0; for (Row row : results) { String errorMessage = checkNotNull(row.getString(1)); if (errorMessagePattern == null && errorMessage.contains(errorMessageFilter) || errorMessagePattern != null && errorMessagePattern.matcher(errorMessage).find()) { count++; } } return count; } @Override public @Nullable HeaderPlus readHeaderPlus(String agentId, String traceId) throws Exception { Trace.Header header = readHeader(agentId, traceId); if (header == null) { return null; } Existence entriesExistence = header.getEntryCount() == 0 ? Existence.NO : Existence.YES; Existence queriesExistence = header.getQueryCount() == 0 ? Existence.NO : Existence.YES; Existence profileExistence = header.getMainThreadProfileSampleCount() == 0 && header.getAuxThreadProfileSampleCount() == 0 ? Existence.NO : Existence.YES; return ImmutableHeaderPlus.builder() .header(header) .entriesExistence(entriesExistence) .queriesExistence(queriesExistence) .profileExistence(profileExistence) .build(); } @Override public Entries readEntries(String agentId, String traceId) throws Exception { return ImmutableEntries.builder() .addAllEntries(readEntriesInternal(agentId, traceId)) .addAllSharedQueryTexts(readSharedQueryTexts(agentId, traceId)) .build(); } @Override public Queries readQueries(String agentId, String traceId) throws Exception { return ImmutableQueries.builder() .addAllQueries(readQueriesInternal(agentId, traceId)) .addAllSharedQueryTexts(readSharedQueryTexts(agentId, traceId)) .build(); } // since this is only used by export, SharedQueryTexts are always returned with fullTrace // (never with truncatedText/truncatedEndText/fullTraceSha1) @Override public EntriesAndQueries readEntriesAndQueriesForExport(String agentId, String traceId) throws Exception { ImmutableEntriesAndQueries.Builder entries = ImmutableEntriesAndQueries.builder() .addAllEntries(readEntriesInternal(agentId, traceId)) .addAllQueries(readQueriesInternal(agentId, traceId)); List<Trace.SharedQueryText> sharedQueryTexts = new ArrayList<>(); for (Trace.SharedQueryText sharedQueryText : readSharedQueryTexts(agentId, traceId)) { String fullTextSha1 = sharedQueryText.getFullTextSha1(); if (fullTextSha1.isEmpty()) { sharedQueryTexts.add(sharedQueryText); } else { String fullText = fullQueryTextDao.getFullText(agentId, fullTextSha1); if (fullText == null) { sharedQueryTexts.add(Trace.SharedQueryText.newBuilder() .setFullText(sharedQueryText.getTruncatedText() + " ... [full query text has expired] ... " + sharedQueryText.getTruncatedEndText()) .build()); } else { sharedQueryTexts.add(Trace.SharedQueryText.newBuilder() .setFullText(fullText) .build()); } } } return entries.addAllSharedQueryTexts(sharedQueryTexts) .build(); } @Override public @Nullable Profile readMainThreadProfile(String agentId, String traceId) throws Exception { Profile profile = readMainThreadProfileUsingPS(agentId, traceId, readMainThreadProfileV2); if (profile != null) { return profile; } return readMainThreadProfileUsingPS(agentId, traceId, readMainThreadProfileV1); } public @Nullable Profile readMainThreadProfileUsingPS(String agentId, String traceId, PreparedStatement readPS) throws Exception { BoundStatement boundStatement = readPS.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); Row row = results.one(); if (row == null) { return null; } return Profile.parseFrom(checkNotNull(row.getBytes(0))); } @Override public @Nullable Profile readAuxThreadProfile(String agentId, String traceId) throws Exception { Profile profile = readAuxThreadProfileUsingPS(agentId, traceId, readAuxThreadProfileV2); if (profile != null) { return profile; } return readAuxThreadProfileUsingPS(agentId, traceId, readAuxThreadProfileV1); } public @Nullable Profile readAuxThreadProfileUsingPS(String agentId, String traceId, PreparedStatement readPS) throws Exception { BoundStatement boundStatement = readPS.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); Row row = results.one(); if (row == null) { return null; } return Profile.parseFrom(checkNotNull(row.getBytes(0))); } private Trace. /*@Nullable*/ Header readHeader(String agentId, String traceId) throws Exception { Trace.Header header = readHeaderUsingPS(agentId, traceId, readHeaderV2); if (header != null) { return header; } return readHeaderUsingPS(agentId, traceId, readHeaderV1); } private Trace. /*@Nullable*/ Header readHeaderUsingPS(String agentId, String traceId, PreparedStatement readPS) throws Exception { BoundStatement boundStatement = readPS.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); Row row = results.one(); if (row == null) { return null; } return Trace.Header.parseFrom(checkNotNull(row.getBytes(0))); } private List<Trace.Entry> readEntriesInternal(String agentId, String traceId) throws Exception { List<Trace.Entry> entries = readEntriesUsingPS(agentId, traceId, readEntriesV2); if (!entries.isEmpty()) { return entries; } return readEntriesUsingPS(agentId, traceId, readEntriesV1); } private List<Trace.Entry> readEntriesUsingPS(String agentId, String traceId, PreparedStatement readPS) throws Exception { BoundStatement boundStatement = readPS.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); List<Trace.Entry> entries = new ArrayList<>(); while (!results.isExhausted()) { Row row = results.one(); int i = 0; Trace.Entry.Builder entry = Trace.Entry.newBuilder() .setDepth(row.getInt(i++)) .setStartOffsetNanos(row.getLong(i++)) .setDurationNanos(row.getLong(i++)) .setActive(row.getBool(i++)); if (row.isNull(i + 1)) { // shared_query_text_index // message is null for trace entries added using addErrorEntry() entry.setMessage(Strings.nullToEmpty(row.getString(i++))); i++; // shared_query_text_index i++; // query_message_prefix i++; // query_message_suffix } else { i++; // message Trace.QueryEntryMessage queryEntryMessage = Trace.QueryEntryMessage.newBuilder() .setSharedQueryTextIndex(row.getInt(i++)) .setPrefix(Strings.nullToEmpty(row.getString(i++))) .setSuffix(Strings.nullToEmpty(row.getString(i++))) .build(); entry.setQueryEntryMessage(queryEntryMessage); } ByteBuffer detailBytes = row.getBytes(i++); if (detailBytes != null) { entry.addAllDetailEntry( Messages.parseDelimitedFrom(detailBytes, Trace.DetailEntry.parser())); } ByteBuffer locationBytes = row.getBytes(i++); if (locationBytes != null) { entry.addAllLocationStackTraceElement(Messages.parseDelimitedFrom(locationBytes, Proto.StackTraceElement.parser())); } ByteBuffer errorBytes = row.getBytes(i++); if (errorBytes != null) { entry.setError(Trace.Error.parseFrom(errorBytes)); } entries.add(entry.build()); } return entries; } private List<Aggregate.Query> readQueriesInternal(String agentId, String traceId) throws Exception { BoundStatement boundStatement = readQueriesV2.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); List<Aggregate.Query> queries = new ArrayList<>(); while (!results.isExhausted()) { Row row = results.one(); int i = 0; Aggregate.Query.Builder query = Aggregate.Query.newBuilder() .setType(checkNotNull(row.getString(i++))) .setSharedQueryTextIndex(row.getInt(i++)) .setTotalDurationNanos(row.getDouble(i++)) .setExecutionCount(row.getLong(i++)); long totalRows = row.getLong(i++); if (!NotAvailableAware.isNA(totalRows)) { query.setTotalRows(OptionalInt64.newBuilder().setValue(totalRows)); } query.setActive(row.getBool(i++)); queries.add(query.build()); } return queries; } private List<Trace.SharedQueryText> readSharedQueryTexts(String agentId, String traceId) throws Exception { List<Trace.SharedQueryText> sharedQueryTexts = readSharedQueryTextsUsingPS(agentId, traceId, readSharedQueryTextsV2); if (!sharedQueryTexts.isEmpty()) { return sharedQueryTexts; } return readSharedQueryTextsUsingPS(agentId, traceId, readSharedQueryTextsV1); } private List<Trace.SharedQueryText> readSharedQueryTextsUsingPS(String agentId, String traceId, PreparedStatement readPS) throws Exception { BoundStatement boundStatement = readPS.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.read(boundStatement); List<Trace.SharedQueryText> sharedQueryTexts = new ArrayList<>(); while (!results.isExhausted()) { Row row = results.one(); int i = 0; String truncatedText = checkNotNull(row.getString(i++)); String truncatedEndText = row.getString(i++); String fullTextSha1 = row.getString(i++); Trace.SharedQueryText.Builder sharedQueryText = Trace.SharedQueryText.newBuilder(); if (fullTextSha1 == null) { sharedQueryText.setFullText(truncatedText); } else { sharedQueryText.setFullTextSha1(fullTextSha1) .setTruncatedText(truncatedText) .setTruncatedEndText(checkNotNull(truncatedEndText)); } sharedQueryTexts.add(sharedQueryText.build()); } return sharedQueryTexts; } @Override @OnlyUsedByTests public void truncateAll() throws Exception { session.updateSchemaWithRetry("truncate table trace_tt_slow_count"); session.updateSchemaWithRetry("truncate table trace_tn_slow_count"); session.updateSchemaWithRetry("truncate table trace_tt_slow_count_partial"); session.updateSchemaWithRetry("truncate table trace_tn_slow_count_partial"); session.updateSchemaWithRetry("truncate table trace_tt_slow_point"); session.updateSchemaWithRetry("truncate table trace_tn_slow_point"); session.updateSchemaWithRetry("truncate table trace_tt_slow_point_partial"); session.updateSchemaWithRetry("truncate table trace_tn_slow_point_partial"); session.updateSchemaWithRetry("truncate table trace_tt_error_count"); session.updateSchemaWithRetry("truncate table trace_tn_error_count"); session.updateSchemaWithRetry("truncate table trace_tt_error_point"); session.updateSchemaWithRetry("truncate table trace_tn_error_point"); session.updateSchemaWithRetry("truncate table trace_tt_error_message"); session.updateSchemaWithRetry("truncate table trace_tn_error_message"); session.updateSchemaWithRetry("truncate table trace_header"); session.updateSchemaWithRetry("truncate table trace_entry"); session.updateSchemaWithRetry("truncate table trace_shared_query_text"); session.updateSchemaWithRetry("truncate table trace_main_thread_profile"); session.updateSchemaWithRetry("truncate table trace_aux_thread_profile"); session.updateSchemaWithRetry("truncate table trace_header_v2"); session.updateSchemaWithRetry("truncate table trace_entry_v2"); session.updateSchemaWithRetry("truncate table trace_query_v2"); session.updateSchemaWithRetry("truncate table trace_shared_query_text_v2"); session.updateSchemaWithRetry("truncate table trace_main_thread_profile_v2"); session.updateSchemaWithRetry("truncate table trace_aux_thread_profile_v2"); } private static void bindSlowPoint(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall, boolean partial, boolean cassandra2x) throws IOException { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall, partial && !cassandra2x); if (partial) { if (cassandra2x) { // don't set real_capture_time, so this still looks like data prior to 0.13.1 boundStatement.setToNull(i++); } else { boundStatement.setTimestamp(i++, new Date(header.getCaptureTime())); } } boundStatement.setLong(i++, header.getDurationNanos()); boundStatement.setBool(i++, header.hasError()); boundStatement.setString(i++, header.getHeadline()); boundStatement.setString(i++, Strings.emptyToNull(header.getUser())); List<Trace.Attribute> attributes = header.getAttributeList(); if (attributes.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(attributes)); } boundStatement.setInt(i++, adjustedTTL); } private static void bindCount(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall, boolean partial, boolean cassandra2x) { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall, partial && !cassandra2x); if (partial) { if (cassandra2x) { // don't set real_capture_time, so this still looks like data prior to 0.13.1 boundStatement.setToNull(i++); } else { boundStatement.setTimestamp(i++, new Date(header.getCaptureTime())); } } boundStatement.setInt(i++, adjustedTTL); } private static void bindErrorMessage(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall, false); boundStatement.setString(i++, header.getError().getMessage()); boundStatement.setInt(i++, adjustedTTL); } private static void bindErrorPoint(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) throws IOException { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall, false); boundStatement.setLong(i++, header.getDurationNanos()); boundStatement.setString(i++, header.getError().getMessage()); boundStatement.setString(i++, header.getHeadline()); boundStatement.setString(i++, Strings.emptyToNull(header.getUser())); List<Trace.Attribute> attributes = header.getAttributeList(); if (attributes.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(attributes)); } boundStatement.setInt(i++, adjustedTTL); } private static int bind(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, boolean overall, boolean useCaptureTimePartialRollup) { int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, header.getTransactionType()); if (!overall) { boundStatement.setString(i++, header.getTransactionName()); } if (useCaptureTimePartialRollup) { boundStatement.setTimestamp(i++, new Date(header.getCaptureTimePartialRollup())); } else { boundStatement.setTimestamp(i++, new Date(header.getCaptureTime())); } boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); return i; } private static void bindThreadProfile(BoundStatement boundStatement, String agentId, String traceId, Profile profile, int adjustedTTL) { int i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setBytes(i++, ByteBuffer.wrap(profile.toByteArray())); boundStatement.setInt(i++, adjustedTTL); } private static void bindTraceQuery(BoundStatement boundStatement, String agentRollupId, TraceQuery query, boolean overall) { int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, query.transactionType()); if (!overall) { boundStatement.setString(i++, query.transactionName()); } boundStatement.setTimestamp(i++, new Date(query.from())); boundStatement.setTimestamp(i++, new Date(query.to())); } private static void bindTraceQueryPartial(BoundStatement boundStatement, String agentRollupId, TraceQuery query, boolean overall, boolean cassandra2x) { int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, query.transactionType()); if (!overall) { boundStatement.setString(i++, query.transactionName()); } if (cassandra2x) { boundStatement.setTimestamp(i++, new Date(query.from())); boundStatement.setTimestamp(i++, new Date(query.to())); } else { // not using getCaptureTimePartialRollup() on "from", to support data prior to 0.13.1 boundStatement.setTimestamp(i++, new Date(query.from())); boundStatement.setTimestamp(i++, new Date(getCaptureTimePartialRollup(query.to()))); boundStatement.setTimestamp(i++, new Date(query.from())); boundStatement.setTimestamp(i++, new Date(query.to())); } } private static long getCaptureTimePartialRollup(long captureTime) { // it's not really relevant that the 30-min interval matches any of the aggregate rollups, // this is just to help reduce proliferation of Cassandra tombstones return CaptureTimes.getRollup(captureTime, MINUTES.toMillis(30)); } private static List<TracePoint> processPoints(ResultSet results, TracePointFilter filter, boolean partial, boolean errorPoints) throws IOException { List<TracePoint> tracePoints = new ArrayList<>(); for (Row row : results) { int i = 0; String agentId = checkNotNull(row.getString(i++)); String traceId = checkNotNull(row.getString(i++)); long captureTime = checkNotNull(row.getTimestamp(i++)).getTime(); if (partial) { // real_capture_time is only present for data written starting with 0.13.1 Date realCaptureTime = row.getTimestamp(i++); if (realCaptureTime != null) { captureTime = realCaptureTime.getTime(); } } long durationNanos = row.getLong(i++); boolean error = errorPoints || row.getBool(i++); // error points are defined by having an error message, so safe to checkNotNull String errorMessage = errorPoints ? checkNotNull(row.getString(i++)) : ""; // headline is null for data inserted prior to 0.9.7 String headline = Strings.nullToEmpty(row.getString(i++)); String user = Strings.nullToEmpty(row.getString(i++)); ByteBuffer attributeBytes = row.getBytes(i++); List<Trace.Attribute> attrs = Messages.parseDelimitedFrom(attributeBytes, Trace.Attribute.parser()); Map<String, List<String>> attributes = attrs.stream().collect( Collectors.toMap(Trace.Attribute::getName, Trace.Attribute::getValueList)); if (filter.matchesDuration(durationNanos) && filter.matchesHeadline(headline) && filter.matchesError(errorMessage) && filter.matchesUser(user) && filter.matchesAttributes(attributes)) { tracePoints.add(ImmutableTracePoint.builder() .agentId(agentId) .traceId(traceId) .captureTime(captureTime) .durationNanos(durationNanos) .partial(partial) .error(error) .checkLiveTraces(false) .build()); } } return tracePoints; } private static Result<TracePoint> combine(List<TracePoint> completedPoints, List<TracePoint> partialPoints, int limit) { if (partialPoints.isEmpty()) { // optimization of common path return createResult(completedPoints, limit); } removeDuplicatePartialPoints(completedPoints, partialPoints); List<TracePoint> allPoints = new ArrayList<>(completedPoints.size() + partialPoints.size()); allPoints.addAll(completedPoints); allPoints.addAll(partialPoints); if (allPoints.size() > limit) { allPoints = applyLimitByDurationNanosAndThenSortByCaptureTime(allPoints, limit); return new Result<>(allPoints, true); } else { // sort by capture time needed since combined partial points are out of order allPoints = Ordering.from(Comparator.comparingLong(TracePoint::captureTime)) .sortedCopy(allPoints); return new Result<>(allPoints, false); } } private static Result<TracePoint> createResult(List<TracePoint> tracePoints, int limit) { if (tracePoints.size() > limit) { return new Result<>( applyLimitByDurationNanosAndThenSortByCaptureTime(tracePoints, limit), true); } else { return new Result<>(tracePoints, false); } } private static void removeDuplicatePartialPoints(List<TracePoint> completedPoints, List<TracePoint> partialPoints) { // remove duplicates (partially stored traces) since there is (small) window between updated // insert (with new capture time) and the delete of prior insert (with prior capture time) Set<TraceKey> traceKeys = new HashSet<>(); for (TracePoint completedPoint : completedPoints) { traceKeys.add(TraceKey.from(completedPoint)); } ListIterator<TracePoint> i = partialPoints.listIterator(partialPoints.size()); while (i.hasPrevious()) { if (!traceKeys.add(TraceKey.from(i.previous()))) { i.remove(); } } } public static List<TracePoint> applyLimitByDurationNanosAndThenSortByCaptureTime( List<TracePoint> tracePoints, int limit) { return tracePoints.stream() .sorted(Comparator.comparingLong(TracePoint::durationNanos).reversed()) .limit(limit) .sorted(Comparator.comparingLong(TracePoint::captureTime)) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<TracePoint>toList()); } private static boolean matches(ErrorMessageFilter filter, String errorMessage) { String upper = errorMessage.toUpperCase(Locale.ENGLISH); for (String include : filter.includes()) { if (!upper.contains(include.toUpperCase(Locale.ENGLISH))) { return false; } } for (String exclude : filter.excludes()) { if (upper.contains(exclude.toUpperCase(Locale.ENGLISH))) { return false; } } return true; } @Value.Immutable abstract static class TraceKey { abstract String agentId(); abstract String traceId(); private static TraceKey from(TracePoint tracePoint) { return ImmutableTraceKey.builder() .agentId(tracePoint.agentId()) .traceId(tracePoint.traceId()) .build(); } } private static class MutableLong { private long value; private void increment() { value++; } } }