Compare commits

...
Author SHA1 Message Date
John Colvin 22bc4bb612 check pt: Replace static libspark init w/ SparkInitializer object 2023-04-18 14:54:25 +00:00
John Colvin 02ba484974 chore: Remove unused code 2023-04-18 14:54:25 +00:00
John Colvin bc290e5bc6 diag: Add verbose opt to spark cli
Applies to logging during startup, until [logging] config is initialized
2023-04-18 14:54:25 +00:00
John Colvin 752b9426a8 chore: Add comparison ops for log Level, Subsystem enums 2023-04-18 14:54:25 +00:00
John Colvin 81529fdbe7 chore: Remove irrelevant test for config fallback url
The 'fallback url' was removed from config a few months ago (this test should
have been removed with it)
2023-04-18 14:54:25 +00:00
John Colvin 7dba037980 chore: Remove unused args/code
* Removes several 'if' blocks whose predicate were effectively compile-time
  constants (i.e., unnecessary).
2023-04-18 14:54:25 +00:00
John Colvin a7e684c720 chore: Remove redundant fn 'get_thread_name' 2023-04-18 14:54:25 +00:00
31 changed files with 463 additions and 271 deletions

No files matched your search

@@ -1,14 +1,15 @@
namespace Spark.Console {
using bts;
using mbts.config;
using Spark.Core;
using System;
using System.Linq;
class ConfigCommand {
public static int run(ConfigCommandOptions opts) {
Configuration.initialize(opts.sparkd);
using var spark_init = new SparkInitializer(new(opts.sparkd));
spark_init.init();
var keys = opts.keys?.ToArray() ?? Array.Empty<string>();
var excluded_keys = opts.excluded_keys?.ToArray() ?? Array.Empty<string>();
return Configuration.run_config_query(
+4 -2
View File
@@ -16,8 +16,10 @@ using System.Composition;
namespace Spark {
class ShellServices {
public ShellServices([Import("config_source")]string config_source) =>
context = new(config_source);
public ShellServices(
[Import("config_source")] string config_source, [Import("verbose")] bool verbose) {
context = new(config_source, verbose);
}
[Export]
public ServiceContext context { get; }
+3 -2
View File
@@ -23,8 +23,9 @@
string user = null,
string password = null,
string config_source = null,
bool? restore_workspace = null
) : base(config_source) {
bool? restore_workspace = null,
bool verbose = false
) : base(config_source, verbose) {
// Get the LoginModel instance on this thread to make sure it performs syncronous config initialization.
var login_model = LoginModel.init(config_source);
// Show the dialog on a background thread so animations aren't blocked during the main application startup
@@ -42,6 +42,11 @@ namespace Spark.Cli {
[Option(_SPARKD_OPT_NAME, MetaValue = "URL", HelpText = "The config server url")]
public string? sparkd { get; set; }
[Option(
"verbose",
HelpText = "Enable verbose logging during startup (until [logging] config is loaded)")]
public bool verbose { get; set; }
/// <summary>
/// Conform opts to modern cli for backwards compatibility
/// </summary>
@@ -0,0 +1,13 @@
#nullable enable
namespace Spark.Cli {
using System.Composition;
[Export]
class SparkCmdOptsExport {
[Export("config_source")]
public string? config_source { get; set; }
[Export("verbose")]
public bool verbose { get; set; }
}
}
@@ -7,6 +7,8 @@ using mbts.ui.shell;
using NUnit.Framework;
using Spark.Core;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -23,12 +25,18 @@ using System.Windows.Threading;
namespace mbts.ui.tests {
[SetUpFixture]
public class ApplicationSetup {
static SparkInitializer _spark_init;
[OneTimeSetUp]
public static void setup() {
// This is required to satisfy the StaticResources in various parts of the application
if (Application.Current == null) {
AutoResetEvent gonow = new AutoResetEvent(false);
Configuration.initialize();
if (_spark_init is not null) {
throw new InvalidOperationException("spark_init already created");
}
_spark_init = new(new());
_spark_init.init();
Thread t = new Thread(() => {
try {
Assert.AreEqual(Thread.CurrentThread.GetApartmentState(), ApartmentState.STA);
@@ -61,6 +69,7 @@ namespace mbts.ui.tests {
if (Application.Current != null && Application.Current.Dispatcher != null) {
Application.Current.Dispatcher.InvokeShutdown();
}
_spark_init?.Dispose();
}
}
@@ -10,6 +10,7 @@ using System.Windows.Threading;
namespace mbts.ui {
public abstract class BTSApplicationBootstrapper : IDisposable {
readonly string _config_source;
readonly bool _verbose;
private ShellModel _model;
@@ -18,8 +19,9 @@ namespace mbts.ui {
Dispatcher.CurrentDispatcher.UnhandledException += CurrentDispatcher_HandleClipboardError;
}
protected BTSApplicationBootstrapper(string config_source = null) {
protected BTSApplicationBootstrapper(string config_source = null, bool verbose = false) {
_config_source = config_source;
_verbose = verbose;
}
public static void CurrentDispatcher_HandleClipboardError(object sender, DispatcherUnhandledExceptionEventArgs e) {
@@ -43,7 +45,7 @@ namespace mbts.ui {
public void Run() {
_model = new ShellModel();
_model.Initialize(_config_source);
_model.Initialize(_config_source, _verbose);
CreateShell(_model);
}
+7 -10
View File
@@ -14,6 +14,7 @@
using mbts.ui.shell;
using Spark.Api.Reactive;
using Spark.Cli;
using Spark.Core.Data;
using System;
@@ -28,12 +29,6 @@
using System.Runtime.Loader;
using System.Text;
[Export]
public class ConfigSourceExporter {
[Export("config_source")]
public string config_source { get; set; }
}
// Models a generic multi-window host
public class ShellModel : IDisposable {
Lazy<CompositionHost> _container = new Lazy<CompositionHost>(create_container);
@@ -59,8 +54,8 @@
public ISecurityMetadataStore security_metadata_store =>
SecurityMetadataStore.instance;
public void Initialize(string config_source) {
PerformMEFComposition(config_source);
public void Initialize(string config_source, bool verbose) {
PerformMEFComposition(config_source, verbose);
InitInitializers();
}
@@ -99,12 +94,14 @@
return value;
}
void PerformMEFComposition(string config_source) {
void PerformMEFComposition(string config_source, bool verbose) {
Log.Ui.info($"Generating MEF dependencies for the shell " +
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
$"{typeof(IShellDaemon)}s, etc.)");
try {
container.GetExport<ConfigSourceExporter>().config_source = config_source;
var cmd_opts = container.GetExport<SparkCmdOptsExport>();
cmd_opts.config_source = config_source;
cmd_opts.verbose = verbose;
container.SatisfyImports(this);
} catch (ReflectionTypeLoadException rtlex) {
var message = new StringBuilder(rtlex.ToString());
+9 -4
View File
@@ -7,13 +7,14 @@ namespace bts.api.core {
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
using Spark.Core;
public sealed class ServiceContext : Spark.Api.IServiceProvider, IDisposable {
IntPtr _qbus;
AuthToken? _token;
public ServiceContext(string? config_source = null) {
BTS.init(config_source);
public ServiceContext(SparkInitializer initializer) {
initializer.init();
_qbus = ApiNativeMethods.api_create_quickbus(null, 0);
}
@@ -26,9 +27,13 @@ namespace bts.api.core {
internal IntPtr native_context => _qbus;
public static ServiceContext create(string? config_server = null) {
public static ServiceContext create(
string? config_server = null,
LogLevel console_level = LogLevel.NoLog,
LogLevel syslog_level = LogLevel.NoLog
) {
var args = Environment.GetCommandLineArgs();
BTS.init(config_server, args);
BTS.init(config_server, console_level, syslog_level, args);
var qbus = ApiNativeMethods.api_create_quickbus(null, 0);
return new ServiceContext(qbus);
}
+77
View File
@@ -0,0 +1,77 @@
#nullable enable
namespace Spark.Core {
using bts;
using bts.utils;
using mbts.config;
using Spark.Core.Threading;
using System;
using System.Text;
using System.Threading;
public sealed class SparkInitializer : InstanceLimiter<SparkInitializer>, IInstanceLimiter<SparkInitializer>, IDisposable {
readonly SparkInitializerOpts _opts;
int _state;
enum State {
Uninitialized = 0,
Initialized = 1,
Disposed = 2
}
public SparkInitializer(SparkInitializerOpts opts) {
_opts = opts;
}
~SparkInitializer() =>
Dispose();
public void init() {
if (_opts.is_test) {
Configuration.initialize_for_test(
_opts.config_source ?? string.Empty, (int)LogLevel.NoLog, (int)LogLevel.NoLog);
}
// Only perform initialization if current state is 'Uninitialized'
if (Interlocked.CompareExchange(
ref _state, (int)State.Initialized, (int)State.Uninitialized) != (int)State.Uninitialized) {
return;
}
BTSNativeMethods.provide_command_line(_opts.args, _opts.args.Length);
var buffer = new StringBuilder(4096);
bool config_ok = BTSNativeMethods.initialize_nothrow(
_opts.config_source ?? string.Empty,
(int)_opts.console_level,
(int)_opts.syslog_level,
buffer,
buffer.Capacity);
if (!config_ok) {
throw new ApplicationException(buffer.ToString());
}
BTSNativeMethods.init_spark_lib();
}
static int IInstanceLimiter<SparkInitializer>.max_instances => 1;
// Idempotent, thread-safe
public void Dispose() {
var prev_state = Interlocked.Exchange(ref _state, (int)State.Disposed);
switch ((State)prev_state) {
case State.Uninitialized:
// Nothing to dispose
break;
case State.Initialized:
// Tear down spark_lib
BTSNativeMethods.finit_spark_lib();
break;
default:
// Unexpected state
throw new InvalidOperationException(
$"Invalid/unknown SparkInitializer state '{prev_state}'");
}
}
}
}
@@ -0,0 +1,35 @@
#nullable enable
namespace Spark.Core {
using bts;
using System;
public readonly struct SparkInitializerOpts {
public SparkInitializerOpts(
string? config_source = null,
LogLevel console_level = LogLevel.NoLog,
LogLevel syslog_level = LogLevel.NoLog,
bool is_test = false)
: this(config_source, console_level, syslog_level, is_test, Environment.GetCommandLineArgs()) { }
public SparkInitializerOpts(
string? config_source,
LogLevel console_level,
LogLevel syslog_level,
bool is_test,
string[] args) {
this.config_source = config_source;
this.console_level = console_level;
this.syslog_level = syslog_level;
this.is_test = is_test;
this.args = args;
}
public string? config_source { get; }
public LogLevel console_level { get; }
public LogLevel syslog_level { get; }
public bool is_test { get; }
public string[] args { get; }
}
}
@@ -0,0 +1,40 @@
#nullable enable
namespace Spark.Core.Threading {
using System;
using System.Threading;
public interface IInstanceLimiter<T> where T : IInstanceLimiter<T> {
private static int _num_instances;
static virtual int max_instances { get; } = 0;
static virtual bool decrement_on_disposed { get; } = false;
static virtual void on_created() {
var max_instances = T.max_instances;
if (Interlocked.Increment(ref _num_instances) >= max_instances) {
throw new InvalidOperationException(
$"Only {max_instances} instances of type '{typeof(T)}' allowed at a time");
}
}
static virtual void on_disposed() {
if (!T.decrement_on_disposed) {
return;
}
if (Interlocked.Decrement(ref _num_instances) < 0) {
throw new InvalidOperationException(
$"More instances of type '{typeof(T)}' have been destroyed than were created in " +
"the first place (invalid internal state)");
}
}
}
public abstract class InstanceLimiter<T> where T : IInstanceLimiter<T> {
protected InstanceLimiter() {
T.on_created();
}
~InstanceLimiter() {
T.on_disposed();
}
}
}
@@ -0,0 +1,31 @@
#nullable enable
namespace Spark.Core.Threading {
using System.Threading;
/// <summary>
/// A thread-safe toggle that can be flipped atomically
/// </summary>
struct InterlockedBool {
int _int_value;
public bool is_enabled => _int_value == 1;
public bool value {
get => _int_value == 1;
set => toggle_to(value);
}
/// <summary>
/// Sets the undelrying value to the specified <paramref name="value"/>
/// </summary>
/// <returns>
/// True if the underlying value was changed, otherwise false (meaning the underlying value
/// was already equal to the specified <paramref name="value"/>).
/// </returns>
public bool toggle_to(bool value) {
var (newval, oldval) = value ? (1, 0) : (0, 1);
return Interlocked.CompareExchange(ref _int_value, newval, oldval) == oldval;
}
}
}
@@ -65,12 +65,9 @@ namespace mbts.config {
_state = State.OK;
}
public static void initialize_for_test(string config = "") {
_raw_initialize_for_test(config);
// still need init_spark_lib
BTS.init();
_state = State.OK;
}
[DllImport("libspark")]
public static extern void initialize_for_test(
string buffer, int console_level, int syslog_level);
public static unsafe bool try_initialize(out string error) {
_reset();
@@ -127,9 +124,6 @@ namespace mbts.config {
[DllImport("libspark", EntryPoint = "initialize_nothrow")]
static extern bool _initialize_nothrow(StringBuilder buffer, int length);
[DllImport("libspark", EntryPoint = "initialize_for_test")]
static extern void _raw_initialize_for_test(string buffer);
[DllImport("libspark", EntryPoint = "reset")]
static extern void _reset();
}
+16 -32
View File
@@ -9,17 +9,29 @@ namespace bts
{
static int _needs_init_spark_lib = 0;
public static void init(string config_source = null) {
public static void init(string config_source = null, bool verbose = false) {
var args = Environment.GetCommandLineArgs();
init(config_source, args);
var (console_level, syslog_level) = verbose ?
(LogLevel.Info, LogLevel.Diag) :
(LogLevel.NoLog, LogLevel.NoLog);
init(config_source, console_level, syslog_level, args);
}
public static void init(string config_source = null, params string[] args) {
public static void init(
string config_source = null,
LogLevel console_level = LogLevel.NoLog,
LogLevel syslog_level = LogLevel.NoLog,
params string[] args
) {
utils.BTSNativeMethods.provide_command_line(args, args.Length);
var buffer = new StringBuilder(4096);
bool config_ok = utils.BTSNativeMethods.initialize_nothrow(
config_source ?? string.Empty, buffer, buffer.Capacity);
config_source ?? string.Empty,
(int)console_level,
(int)syslog_level,
buffer,
buffer.Capacity);
if (!config_ok) {
throw new ApplicationException(buffer.ToString());
}
@@ -34,34 +46,6 @@ namespace bts
// };
}
/// <summary>
/// Adds or replaces the '-configserver' argument with <paramref name="config_server"/>
/// </summary>
/// <remarks>
/// TODO: Clean up the cli, or consider getting rid of it and simply using config files as
/// the exclusive means to pass arguments. The configserver override is particularly awkward
/// because it is processed independently in c++ (see ConfigurationState::settings_path).
/// </remarks>
public static IEnumerable<string> set_config_arg(
string[] args, string config_server) {
const string CONFIG_SERVER_OPTION = "-configserver";
bool has_emitted_cfg_server = false;
for (int i = 0; i < args.Length;) {
yield return args[i];
if (args[i] == CONFIG_SERVER_OPTION) {
has_emitted_cfg_server = true;
yield return config_server;
i += 2;
continue;
}
i++;
}
if (!has_emitted_cfg_server) {
yield return CONFIG_SERVER_OPTION;
yield return config_server;
}
}
internal static IEnumerable<string> remove_config_arg(string[] args) {
int skip = 0;
foreach (var arg in args) {
@@ -28,6 +28,10 @@ namespace bts.utils
[DllImport("libspark")]
public static extern bool initialize_nothrow(
string config_source, StringBuilder buffer, int length);
string config_source,
int console_level,
int syslog_level,
StringBuilder buffer,
int length);
}
}
+2 -1
View File
@@ -37,7 +37,8 @@ CAPI Quickbus *api_create_quickbus(const char *qbconfig, long length) {
// If config hasn't been initialized yet do it now and suppress exceptions.
// When we're called from a nuget package we may not have defaults which can
// cause errors
config::initialize_nothrow("", nullptr, 0);
config::initialize_nothrow(
"", bts::Error.ordinal, bts::Warn.ordinal, nullptr, 0);
config::QuickbusNodeCfg cfg;
if (length > 0 && qbconfig != nullptr &&
+32 -19
View File
@@ -6,17 +6,8 @@ namespace bts {
namespace config {
namespace internal {
struct ConfigLog::PImpl {
PImpl()
: print_errors(true), logger_hub("config_logger"),
logger(logger_hub.make_logger()) {}
bool print_errors;
log::LoggerHub logger_hub;
log::Logger &logger;
};
ConfigLog::ConfigLog() : me(new PImpl()) {
static log::LoggingConfig mk_config(
log::Level console_level, log::Level syslog_level) {
log::LoggingConfig cfg;
cfg.level_names = {{Diag, "Diag"}, {Info, "Info"}, {Warn, "Warn"},
{Error, "Error"}, {Fatal, "Fatal"}, {Sys, "Sys"}};
@@ -29,12 +20,31 @@ ConfigLog::ConfigLog() : me(new PImpl()) {
l = Warn;
cfg.deployment_type = "Config";
f[log::Cfg.ordinal] = Warn;
f[log::Cfg.ordinal] = console_level;
cfg.sinks["console"] = std::make_pair(log::console_sink(cfg), f);
f[log::Cfg.ordinal] = Info;
f[log::Cfg.ordinal] = syslog_level;
cfg.sinks["syslog"] = std::make_pair(log::syslog_sink(cfg), f);
me->logger_hub.config(cfg);
return cfg;
}
struct ConfigLog::PImpl {
PImpl()
: is_enabled(true), logger_hub("config_logger"),
logger(logger_hub.make_logger()) {}
bool is_enabled;
log::LoggerHub logger_hub;
log::Logger &logger;
};
ConfigLog::ConfigLog(bool verbose)
: ConfigLog(verbose ? Diag : Warn, verbose ? Diag : Info, verbose) {}
ConfigLog::ConfigLog(
bts::log::Level console_level, bts::log::Level syslog_level, bool is_enabled)
: me(std::make_unique<PImpl>()) {
me->is_enabled = is_enabled;
me->logger_hub.config(mk_config(console_level, syslog_level));
start();
}
@@ -42,25 +52,28 @@ void ConfigLog::start() { me->logger_hub.start(); }
ConfigLog::~ConfigLog() { me->logger.mark_inactive(); }
bool ConfigLog::print_errors() const { return me->print_errors; }
void ConfigLog::set_print_errors(bool value) { me->print_errors = value; }
bool ConfigLog::is_enabled() const { return me->is_enabled; }
void ConfigLog::set_is_enabled(bool value) { me->is_enabled = value; }
void ConfigLog::info(const std::string &msg) {
if (me->print_errors)
if (me->is_enabled) {
me->logger.enqueue(
log::log_ts(), log::Cfg, Info, __FILE__, __LINE__, "{0}", msg);
}
}
void ConfigLog::error(const std::string &msg) {
if (me->print_errors)
if (me->is_enabled) {
me->logger.enqueue(
log::log_ts(), log::Cfg, Error, __FILE__, __LINE__, "{0}", msg);
}
}
void ConfigLog::warning(const std::string &msg) {
if (me->print_errors)
if (me->is_enabled) {
me->logger.enqueue(
log::log_ts(), log::Cfg, Warn, __FILE__, __LINE__, "{0}", msg);
}
}
//
+9 -3
View File
@@ -1,5 +1,6 @@
#pragma once
#include "lib/logging/Common.h"
#include "lib/utils/bts_no_copy.h"
#include <memory>
@@ -22,15 +23,20 @@ class IConfigLog {
// To allow config diagnostic logging
class ConfigLog : public IConfigLog {
public:
ConfigLog();
explicit ConfigLog(bool verbose = false);
ConfigLog(
bts::log::Level console_level,
bts::log::Level syslog_level,
bool is_enabled);
~ConfigLog();
void info(const std::string &msg);
void error(const std::string &msg);
void warning(const std::string &msg);
bool print_errors() const;
void set_print_errors(bool value);
bool is_enabled() const;
void set_is_enabled(bool value);
private:
void start();
+2 -1
View File
@@ -97,7 +97,8 @@ void run_config_query(
void ConfigQuerier::init_(bool verbose) {
char buffer[4096] = {0};
utils::TimeStamp start = utils::TimeStamp::now();
bool success = config::initialize_nothrow("", buffer, sizeof(buffer));
bool success = config::initialize_nothrow(
"", bts::Warn.ordinal, bts::Warn.ordinal, buffer, sizeof(buffer));
double time = (utils::TimeStamp::now() - start).seconds();
if (success) {
+13 -9
View File
@@ -9,6 +9,7 @@
#include "lib/collections/Contains.h"
#include "lib/core/FMT.h"
#include "lib/io/ProtoPrinter.h"
#include "lib/logging/logging.h"
#include "lib/os/os.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
@@ -357,7 +358,7 @@ void check_rule(
}
void ConfigurationState::check_rules() {
initialize("");
initialize("", bts::Error, bts::Warn);
std::vector<std::shared_ptr<proto::Message>>::iterator iter;
for (iter = _config_chain.begin(); iter != _config_chain.end(); ++iter) {
proto::Message *message = (*iter).get();
@@ -366,15 +367,16 @@ void ConfigurationState::check_rules() {
check_rule(_defaults.get(), _rule_engine);
}
void ConfigurationState::initialize_settings(string config_source) {
void ConfigurationState::initialize_settings(
string config_source,
bts::log::Level console_verbosity,
bts::log::Level syslog_verbosity) {
std::lock_guard<std::recursive_mutex> lock(_initialization_mutex);
if (_is_initialized)
return;
ConfigLog log;
ConfigLog log{console_verbosity, syslog_verbosity, true};
log.info(FMT("Initializing settings from '{}'", config_source));
// For verbose config logs, uncomment line below
// log.set_print_errors(true);
initialize_defaults(log);
if (config_source.empty()) {
@@ -420,7 +422,6 @@ int ConfigurationState::load(const std::string &url, IConfigLog &log) {
std::shared_ptr<proto::Message> new_message(_prototype->New());
bool loaded = false;
log.info(FMT("Loading '{}'", url));
loaded = load_message(url, *new_message.get(), log, *_deferred_queue.get());
if (_host.empty() || _host == "localhost") {
const auto old_host = _host;
@@ -450,9 +451,12 @@ int ConfigurationState::load(const std::string &url, IConfigLog &log) {
}
int ConfigurationState::load_string(
const std::string &values, bool print_errors) {
const std::string &values,
log::Level console_verbosity,
log::Level syslog_verbosity) {
ConfigLog log;
log.set_print_errors(print_errors);
log.set_is_enabled(
console_verbosity != bts::NoLog || syslog_verbosity != bts::NoLog);
initialize_defaults(log);
std::shared_ptr<proto::Message> new_message(_prototype->New());
@@ -939,7 +943,7 @@ int load_directory(
IConfigLog &log,
ConfigValueQueue &lookup) {
LogAggregator aggregate_log(log);
aggregate_log.info(FMT("Loading config dir from '{}'", url));
aggregate_log.info(FMT("Loading config dir '{}'", url));
const char last_char = url.at(url.length() - 1);
if (last_char != '/') {
+9 -2
View File
@@ -7,6 +7,7 @@
#include "ConfigurationException.h"
#include "lib/expression/ExpressionEvaluator.h"
#include "lib/expression/Value.h"
#include "lib/logging/logging.h"
#include "text_format/text_format.h"
#include <google/protobuf/descriptor.h>
@@ -60,10 +61,16 @@ class ConfigurationState {
* @param config_source The source from which to load values. Can be a path
* (file or dir), or URL (config server).
*/
void initialize_settings(std::string config_source = "");
void initialize_settings(
std::string config_source = "",
bts::log::Level console_verbosity = bts::NoLog,
bts::log::Level syslog_verbosity = bts::NoLog);
int load(const std::string &url, IConfigLog &);
int load_string(const std::string &values, bool print_errors = false);
int load_string(
const std::string &values,
log::Level console_verbosity = bts::NoLog,
log::Level syslog_verbosity = bts::NoLog);
void reset();
std::vector<const proto::FieldDescriptor *> get_fields(
+31 -10
View File
@@ -20,6 +20,8 @@ namespace bts {
namespace config {
using std::string;
using std::int32_t;
using LogLevel = bts::log::Level;
internal::ConfigurationState config_state;
@@ -27,18 +29,32 @@ internal::ConfigurationState config_state;
// Public API
//////////////////////////////////////////
extern "C" BTS_EXPORT void initialize(const char *config_source) {
config_state.initialize_settings(string{config_source});
extern "C" BTS_EXPORT void initialize(
const char *config_source, int32_t console_level, int32_t syslog_level) {
initialize(
string{config_source}, log::log_level_from_int32(console_level),
log::log_level_from_int32(syslog_level));
}
void initialize(
const string &config_source,
LogLevel console_level,
LogLevel syslog_level) {
config_state.initialize_settings(config_source, console_level, syslog_level);
}
extern "C" BTS_EXPORT bool initialize_nothrow(
const char *config_source, char *error_message, size_t len) {
const char *config_source,
int32_t console_level,
int32_t syslog_level,
char *error_message,
std::size_t length) {
try {
initialize(config_source);
initialize(config_source, console_level, syslog_level);
} catch (std::exception &e) {
std::string msg = e.what();
if (msg.size() < len) {
os::strncpy(error_message, msg.c_str(), len);
if (msg.size() < length) {
os::strncpy(error_message, msg.c_str(), length);
}
return false;
}
@@ -103,12 +119,17 @@ namespace internal {
// Functions used in testing
//////////////////////////////////////////
extern "C" BTS_EXPORT void initialize_for_test(const char *config) {
initialize_for_test(std::string(config), false);
extern "C" BTS_EXPORT void initialize_for_test(
const char *config, int32_t console_level, int32_t syslog_level) {
initialize_for_test(
string(config), bts::log::log_level_from_int32(console_level),
bts::log::log_level_from_int32(syslog_level));
}
void initialize_for_test(const std::string &values, bool print_errors) {
void initialize_for_test(
const string &values, LogLevel console_level, LogLevel syslog_level) {
config_state.reset();
config_state.load_string(values, print_errors);
config_state.load_string(values, console_level, syslog_level);
}
extern "C" BTS_EXPORT void reset() { config_state.reset(); }
+22 -4
View File
@@ -1,9 +1,11 @@
#ifndef BTS_CONFIG_H_
#define BTS_CONFIG_H_
#include "lib/logging/logging.h"
#include "lib/utils/bts_export.h"
#include "lib/utils/checked_cast.h"
#include <cstdint>
#include <string>
#include <vector>
@@ -27,12 +29,24 @@ namespace config {
// config can result in network requests this function is available for
// applications that want to manage pauses in execution. Ex: initialize() on a
// background thread so that config::get<T>() is fast on a UI thread
extern "C" BTS_EXPORT void initialize(const char *config_path);
extern "C" BTS_EXPORT void initialize(
const char *config_path,
std::int32_t console_level,
std::int32_t syslog_level);
void initialize(
const std::string &config_source,
bts::log::Level console_level = bts::Error,
bts::log::Level syslog_level = bts::Warn);
// Calls initialize() but catches any thrown exception and copies it to the
// provided buffer
extern "C" BTS_EXPORT bool initialize_nothrow(
const char *config_source, char *error_msg, size_t length);
const char *config_source,
std::int32_t console_level,
std::int32_t syslog_level,
char *error_message,
size_t length);
std::vector<const google::protobuf::FieldDescriptor *> get_fields(
const std::vector<std::string> keys = {},
@@ -73,9 +87,13 @@ namespace internal {
// Initialize configuration state from a string instead of from the filesystem.
// defaults.conf will still be loaded. Calling this a second time clears all
// values from previous initialization
extern "C" BTS_EXPORT void initialize_for_test(const char *config);
extern "C" BTS_EXPORT void initialize_for_test(
const char *config, std::int32_t console_level, std::int32_t syslog_level);
void initialize_for_test(
const std::string &configuration, bool print_errors = true);
const std::string &values,
log::Level console_level = Error,
log::Level syslog_level = Warn);
// Releases all resources and returns config to factory defaults
extern "C" BTS_EXPORT void reset();
+14 -50
View File
@@ -3,6 +3,7 @@
#include "../ConfigurationException.h"
#include "../ConfigurationState.h"
#include "../config.h"
#include "lib/core/FMT.h"
#include "lib/expression/ParserExceptions.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/is_unittest_environment.h"
@@ -97,7 +98,7 @@ TEST_CASE("Configuration Tests", "[config]") {
bool error = false;
try {
initialize_for_test("include: \"" + config_b.path() + "\"", false);
initialize_for_test(FMT(R"(include: "{}")", config_b.path()));
} catch (const ConfigurationException &err) {
std::string what = err.what();
bts_assert(what.find("Expected identifier.") != std::string::npos);
@@ -331,12 +332,13 @@ TEST_CASE("Configuration Tests", "[config]") {
}
SECTION("built_in_properties_test") {
initialize_for_test("Foo { \n"
" rule: \"SystemConfig.unittest_environment == true \"\n"
" value {\n"
" name: \"unit tests\"\n"
" }\n"
"}\n");
initialize_for_test(
"Foo { \n"
" rule: \"SystemConfig.unittest_environment == true \"\n"
" value {\n"
" name: \"unit tests\"\n"
" }\n"
"}\n");
REQUIRE(get<Foo>().name() == "unit tests");
}
@@ -344,13 +346,11 @@ TEST_CASE("Configuration Tests", "[config]") {
SECTION("nonbreaking_space_test") {
bool parse_error = false;
try {
initialize_for_test(
"Foo { "
" value {\xA0\xA0 "
" name: \"valid name\""
" \xA0 }"
"} ",
false);
initialize_for_test("Foo { "
" value {\xA0\xA0 "
" name: \"valid name\""
" \xA0 }"
"} ");
} catch (const bts::config::ConfigurationException &) {
parse_error = true;
}
@@ -407,42 +407,6 @@ TEST_CASE("Configuration Tests", "[config]") {
REQUIRE(config::get<Foo>().name() == "good");
}
SECTION("fallback_test") {
// HTTPServer main;
HTTPServer fallback;
// std::string main_url = "http://localhost:" +
// boost::lexical_cast<std::string>(main.port()) + "/";
std::string main_url = "http://does_not_exist:12345/";
// main.stop();
std::string fallback_url =
"http://127.0.0.1:" +
boost::lexical_cast<std::string>(fallback.port()) + "/";
std::string response = "HTTP/1.0 200 OK\r\n"
"Content-Type: text/plain\r\n\r\n";
std::string content = "FooGroup {\n"
" value {\n"
" group: \"http_group\"\n"
" foo {\n"
" name: \"http\"\n"
" id: 80\n"
"}}}\n";
fallback.add_response("/spark.conf", response, content);
fallback.start();
initialize_for_test(
"include: '" + main_url +
"'\n"
"fallback: '" +
fallback_url + "'\n",
false);
fallback.stop();
REQUIRE(get<FooGroup>().foo().name() == "http");
REQUIRE(get<FooGroup>().foo().id() == 80);
}
SECTION("macro_definition_order_test") {
// The $is_crusible test
// o2_cme_enabled depends on Foo which has a rule which depends on
+20 -2
View File
@@ -11,20 +11,37 @@
namespace bts {
namespace log {
#define COMPARISON_OP(op, type, prop) \
constexpr bool operator##op(const type &lhs, const type &rhs) { \
return lhs.##prop op rhs.##prop; \
}
struct Level {
using Rep = uint8_t;
constexpr static auto Max = (std::numeric_limits<Rep>::max)();
Rep ordinal;
bool operator<(const Level &rhs) const { return ordinal < rhs.ordinal; }
};
COMPARISON_OP(==, Level, ordinal)
COMPARISON_OP(!=, Level, ordinal)
COMPARISON_OP(>, Level, ordinal)
COMPARISON_OP(>=, Level, ordinal)
COMPARISON_OP(<, Level, ordinal)
COMPARISON_OP(<=, Level, ordinal)
struct Subsystem {
using Rep = uint8_t;
constexpr static auto Max = (std::numeric_limits<Rep>::max)();
Rep ordinal;
bool operator<(const Subsystem &rhs) const { return ordinal < rhs.ordinal; }
};
COMPARISON_OP(==, Subsystem, ordinal)
COMPARISON_OP(!=, Subsystem, ordinal)
COMPARISON_OP(>, Subsystem, ordinal)
COMPARISON_OP(>=, Subsystem, ordinal)
COMPARISON_OP(<, Subsystem, ordinal)
COMPARISON_OP(<=, Subsystem, ordinal)
using ArgBuffer = std::array<uint8_t, 156>;
using MsgFormatter = std::string (*)(ArgBuffer const &, const char *);
using Filter = std::array<Level, Subsystem::Max + 1>;
@@ -45,5 +62,6 @@ inline uint64_t log_ts() {
return result;
}
#undef COMPARISON_OP
} // namespace log
} // namespace bts
+26
View File
@@ -8,6 +8,7 @@
#include <atomic>
#include <boost/filesystem/path.hpp>
#include <cstdint>
#include <thread>
namespace bts {
@@ -65,6 +66,8 @@ struct LoggerHandle {
Logger *logger;
};
constexpr Level log_level_from_int32(std::int32_t value);
struct CharPtrWrapper {
const char *ptr;
};
@@ -102,6 +105,29 @@ class LoggingConfigPoller {
boost::filesystem::path log_path();
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr Level log_level_from_int32(std::int32_t value) {
switch (value) {
case bts::Diag.ordinal:
return Diag;
case bts::Info.ordinal:
return Info;
case bts::Warn.ordinal:
return Warn;
case bts::Error.ordinal:
return Error;
case bts::Fatal.ordinal:
return Fatal;
case bts::Sys.ordinal:
return Sys;
default:
return NoLog;
}
}
#define LOG(subsystem, level, ...) \
bts::log::_thread_logger.logger->is_loggable(bts::log::subsystem, level) && \
bts::log::_thread_logger.logger->enqueue( \
+1 -6
View File
@@ -69,13 +69,8 @@ static void execute() {
user::AuthServiceServer login_server(sc);
bts::md::MBOProcessor mbo_processor;
auto *mbo_writer = config::get<config::toggles::PlacementInQueue>().enabled()
? &mbo_processor.writer()
: nullptr;
bts::md::TradeSummarySignalDispatcher tss_dispatcher;
md::cmds::Run::run(
mbo_writer, &tss_dispatcher, md::cmds::Run::MDType::Configured, true,
true);
md::cmds::Run::run();
api::synthetic::SyntheticSecurityService synthetic_service(sc);
bts::md::MDClient mdclient(
+3 -3
View File
@@ -53,9 +53,9 @@ Web::Web()
http::SparkWebServer webserver(sc, stats, perf_log, ports.config_port());
user::AuthServiceServer login_server(sc);
if (_args->md)
md::cmds::Run::run(
nullptr, nullptr, md::cmds::Run::MDType::Configured, true, true);
if (_args->md) {
md::cmds::Run::run();
}
bts::api::synthetic::SyntheticSecurityService synthetic_service(sc);
bts::admin::AdminServiceServer admin_service(sc);
+12 -89
View File
@@ -8,7 +8,6 @@
#include "lib/os/os.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/thread_manager.h"
#include "server/sparkd/exchange_sim/ExchangeSim.h"
#include "server/sparkd/md/platforms/IMDPlatform.h"
#include "server/sparkd/md/platforms/ice_onixs/ICEMain.h"
#include "server/sparkd/md/platforms/mdp3_onixs/MDP3Main.h"
@@ -16,14 +15,6 @@
namespace bts::cmds::md::cmds {
static std::optional<std::thread> _md_thread;
std::string get_thread_name() {
std::string rv;
os::get_thread_name(&rv);
return rv;
}
static void boot_sleep() {
auto conf = config::get<bts::md::MDBootDelayWindow>();
const auto *start = &conf.standard_start();
@@ -38,18 +29,18 @@ static void boot_sleep() {
if (datetime::is_local_time_between(hh_start, mm_start, hh_end, mm_end)) {
auto sleep_period = std::chrono::seconds(conf.delay_seconds());
LOG(md, Info, "Run::boot_sleep t_id{} t_name:{} sleep_period:{}",
os::get_thread_id(), get_thread_name(), sleep_period.count());
os::get_thread_id(), os::get_thread_name(), sleep_period.count());
std::this_thread::sleep_for(sleep_period);
} else {
LOG(md, Info, "Run::boot_sleep t_id{} t_name:{} sleep_period:{}",
os::get_thread_id(), get_thread_name(), 2);
os::get_thread_id(), os::get_thread_name(), 2);
std::this_thread::sleep_for(2s);
}
}
static void run_replay() {
LOG(md, Info, "Run::run_replay t_id:{} t_name", os::get_thread_id(),
get_thread_name());
os::get_thread_name());
const auto &replay_opts = config::get<bts::md::ReplayOptions>();
bts::md::replay_recording(
replay_opts.url(), config::get<bts::md::RelayOptions>().naggle_ms(),
@@ -60,7 +51,7 @@ static void run_onixs(
bts::md::MBOWriter *mbo_writer,
bts::md::TradeSummarySignalDispatcher *tss_dispatcher) {
LOG(md, Info, "Run::run_onixs t_id:{} t_name", os::get_thread_id(),
get_thread_name());
os::get_thread_name());
std::vector<std::shared_ptr<bts::md::IMDPlatform>> platforms;
// get all glbx symbols
std::vector<bts::md::UserSymbol> cme_symbols;
@@ -92,89 +83,21 @@ static void run_onixs(
disp.run();
}
static void run_emumba() {
// bts_assert(false);
// LOG(md, Info, "Run::run_emumba t_id:{} t_name:{}", os::get_thread_id(),
// get_thread_name());
// bts::md::EmumbaMain em_main = {};
// em_main.run();
}
static void run_md_gwy() {
LOG(md, Info, "Run::run_md_gwy t_id:{} t_name:{}", os::get_thread_id(),
get_thread_name());
// bts::md::CCMain cc_main = {};
// cc_main.run();
os::get_thread_name());
bts::md::MarketDataGateway gwy = {};
// this keeps going in a loop
gwy.run();
}
void Run::run(
bts::md::MBOWriter *mbo_writer,
bts::md::TradeSummarySignalDispatcher *tss_dispatcher,
Run::MDType type,
bool cleanup,
bool run_on_md_thread) {
void Run::run() {
md::cmds::Cleanup::execute();
LOG(md, Info, "Run::run cleanup:{} run_on_md_thread:{} t_id:{} t_name:{}",
cleanup, run_on_md_thread, os::get_thread_id(), get_thread_name());
if (type == Run::MDType::Configured &&
!config::get<bts::md::HistoricMarketDataConfig>().use_md()) {
LOG(md, Info, "Ignoring request to run md: 'use_md' is configured false");
return;
}
auto impl = [&mbo_writer, &tss_dispatcher, &type, &cleanup]() {
LOG(md, Info, "Run::impl lambda t_id: {} t_name: {}", os::get_thread_id(),
get_thread_name());
if (cleanup)
md::cmds::Cleanup::execute();
if (type == Run::MDType::Configured) {
const auto &conf = config::get<bts::md::HistoricMarketDataConfig>();
if (conf.use_replay())
type = Run::MDType::Replay;
else if (conf.use_onixs())
type = Run::MDType::Onixs;
}
switch (type) {
case Run::MDType::Replay:
run_replay();
break;
case Run::MDType::Onixs:
run_onixs(mbo_writer, tss_dispatcher);
break;
default:
bts_assert_msg(false, "Unexpected md type");
}
return;
};
if (run_on_md_thread) {
bts_assert(_md_thread == std::nullopt);
// start a thread for the md
// _md_thread =
// std::optional(bts::threading::make_thread("MDPublisher", impl));
bool is_run_emumba = false;
if (is_run_emumba) {
threading::make_thread("Emumba", []() { run_emumba(); }).detach();
}
// if (bts::config::get<bts::md::HistoricMarketDataConfig>().use_sim()) {
// threading::make_thread("ExchangeSimGLBX", [&mbo_writer]() {
// sim::start_exchange_sim(api::Venue::VENUE_SIM, mbo_writer);
// }).detach();
// }
bool is_run_md_gwy = true;
if (is_run_md_gwy) {
threading::make_thread("MarketDataGateway", []() {
run_md_gwy();
}).detach();
}
boot_sleep();
} else {
impl();
}
LOG(md, Info, "Run::run t_id:{} t_name:{}", os::get_thread_id(),
os::get_thread_name());
// start a thread for the md
threading::make_thread("MarketDataGateway", run_md_gwy).detach();
boot_sleep();
}
Run::Run()
@@ -187,7 +110,7 @@ Run::Run()
add_subcommand("onixs", "Run onixs market data")->callback([] {
run_onixs(nullptr, nullptr);
});
callback([] { run(nullptr, nullptr, MDType::Configured, true, true); });
callback([] { run(); });
}
} // namespace bts::cmds::md::cmds
+1 -6
View File
@@ -21,12 +21,7 @@ struct Run : public CLI::App {
/**
* Run market data
*/
static void run(
bts::md::MBOWriter *,
bts::md::TradeSummarySignalDispatcher *,
MDType type,
bool cleanup,
bool run_on_md_thread);
static void run();
};
} // namespace bts::cmds::md::cmds