Commit 87e28057 authored by EXT Vivien Delmon's avatar EXT Vivien Delmon
Browse files

Try a NoState implementation for JsonWriter to improve performances

parent 99abb8bf
/****************************************************************************
* **
* ** Copyright (C) 2016 MinMaxMedical.
* ** All rights reserved.
* ** Contact: MinMaxMedical <InCAS@MinMaxMedical.com>
* **
* ** This file is part of the modmedLog module.
* **
* ** $QT_BEGIN_LICENSE:BSD$
* ** You may use this file under the terms of the BSD license as follows:
* **
* ** "Redistribution and use in source and binary forms, with or without
* ** modification, are permitted provided that the following conditions are
* ** met:
* ** * Redistributions of source code must retain the above copyright
* ** notice, this list of conditions and the following disclaimer.
* ** * Redistributions in binary form must reproduce the above copyright
* ** notice, this list of conditions and the following disclaimer in
* ** the documentation and/or other materials provided with the
* ** distribution.
* ** * Neither the name of MinMaxMedical S.A.S. and its Subsidiary(-ies) nor
* ** the names of its contributors may be used to endorse or promote
* ** products derived from this software without specific prior written
* ** permission.
* **
* ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
* ** $QT_END_LICENSE$
* **
* ****************************************************************************/
#pragma once
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtextstream.h>
#include <QtCore/qstring.h>
class QIODevice;
class QByteArray;
#include <modmed/data/Data.h>
#include <modmed/data/Bind.h>
#include <modmed/data/StringResult.h>
#include <modmed/modmed_global.h>
namespace modmed {
namespace data {
template <Operator Op, typename OutType, typename T>
struct BindNoState;
template <typename OutType, typename T>
struct BindDispatchNoState
{
static void bind(Itm<OutType> itm, T& d)
{
BindNoState<OutType::OperatorValue, OutType, T>::bind(itm, d);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, QString>
{
static void bind(Itm<OutType> itm, QString& txt)
{
itm.state()->bindText(txt);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, QDateTime>
{
static void bind(Itm<OutType> itm, QDateTime& d)
{
itm.state()->bindBytes(d);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, QByteArray>
{
static void bind(Itm<OutType> itm, QByteArray& b)
{
itm.state()->bindBytes(b);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, bool>
{
static void bind(Itm<OutType> itm, bool& b)
{
itm.state()->bindBoolean(b);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, char>
{
static void bind(Itm<OutType> itm, char& n)
{
qlonglong num = n;
itm.state()->bindNumber(num);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, int>
{
static void bind(Itm<OutType> itm, int& n)
{
qlonglong num = n;
itm.state()->bindNumber(num);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, float>
{
static void bind(Itm<OutType> itm, float& f)
{
double d = f;
itm.state()->bindNumber(d);
}
};
template <typename OutType>
struct BindDispatchNoState<OutType, double>
{
static void bind(Itm<OutType> itm, double& f)
{
double d = f;
itm.state()->bindNumber(d);
}
};
template <Operator Op, typename OutType, typename TKey, typename T>
struct BindNoState<Op, typename OutType, QMap<TKey, T>>
{
static void bind(Itm<OutType> d, QMap<TKey, T>& userMap)
{
auto s = d.sequence();
for (auto userItems = userMap.begin(); userItems != userMap.end(); ++userItems)
{
s.record()
.bind(QStringLiteral("key" ), userItems.key ())
.bind(QStringLiteral("value"), userItems.value())
.out();
}
s.out();
}
};
template<Operator Op, typename OutType, typename T, size_t Size>
struct BindNoState<Op, OutType, T[Size]>
{
static void bind(Itm<OutType> data, T(&userData)[Size])
{
auto s = data.sequence();
for (unsigned i = 0; i < Size; ++i)
{ s = s.bind(userData[i]); }
s.out();
}
};
struct JsonWriterNoStatePrivate;
/// <summary> Serialize using the JSON format </summary>
//!
//! - Sequence is represented by items between '[' ']' separated by ','
//! - Record is represented by items indexed by strings between '{' '}' separated by ','
//! - Null is represented by the null item
//!
//! \b Responsibility: Read and write data structures using the Json format (\ref dataJson)
class MODMED_API JsonWriterNoState
{
//Q_DISABLE_COPY(JsonWriterNoState)
public:
static void escape(QTextStream& jsonStream, const QString& t);
static void escape(QTextStream& jsonStream, unsigned ucs4);
static void escapeUtf8(QIODevice &jsonUtf8, QByteArray utf8);
/// <summary> Construct an JSON serializer from a QTextStream </summary>
/// <param name="p_s"> QTextStream that is written </param>
/// <param name="p_documentInformation"> JSON preamble that is passed to the #document method </param>
/// <param name="p_maxIndentLevel"> maximum JSON nesting level that will be written on a new line and indented </param>
/// <param name="p_maxIndentLevelSeparator"> if not '\0', the character is used to separate items at p_maxIndentLevel JSON nesting level </param>
JsonWriterNoState(QTextStream* p_s
, const QString& p_documentInformation = QString()
, unsigned short p_maxIndentLevel = 1
, char p_maxIndentLevelSeparator = '\t');
/// <summary> Construct an JSON serializer from a QIODevice </summary>
/// <param name="p_io"> QIODevice where to write utf-8 JSON </param>
/// <param name="p_documentInformation"> JSON preamble that is passed to the #document method </param>
/// <param name="p_maxIndentLevel"> maximum JSON nesting level that will be written on a new line and indented </param>
/// <param name="p_maxIndentLevelSeparator"> if not '\0', the character is used to separate items at p_maxIndentLevel JSON nesting level </param>
JsonWriterNoState(QIODevice* p_io
, const QString& p_documentInformation = QString()
, unsigned short p_maxIndentLevel = 1
, char p_maxIndentLevelSeparator = '\t');
/// <summary> Construct an JSON serializer from a File* </summary>
/// <param name="p_file"> File handler that is read or written </param>
/// <param name="p_documentInformation"> JSON preamble that is passed to the #document method </param>
/// <param name="p_maxIndentLevel"> maximum JSON nesting level that will be written on a new line and indented </param>
/// <param name="p_maxIndentLevelSeparator"> if not '\0', the character is used to separate items at p_maxIndentLevel JSON nesting level </param>
JsonWriterNoState(FILE* p_file
, const QString& p_documentInformation = QString()
, unsigned short p_maxIndentLevel = 1
, char p_maxIndentLevelSeparator = '\t');
/// <summary> Construct an JSON serializer from a QString </summary>
/// <param name="p_string"> QString that is read or written </param>
/// <param name="p_documentInformation"> JSON preamble that is passed to the #document method </param>
/// <param name="p_maxIndentLevel"> maximum JSON nesting level that will be written on a new line and indented </param>
/// <param name="p_maxIndentLevelSeparator"> if not '\0', the character is used to separate items at p_maxIndentLevel JSON nesting level </param>
JsonWriterNoState(QString* p_string
, const QString& p_documentInformation = QString()
, unsigned short p_maxIndentLevel = 1
, char p_maxIndentLevelSeparator = '\t');
/// <summary> Destructor </summary>
virtual ~JsonWriterNoState();
/// <summary> Call flush on the internal QTextStream </summary>
void flush();
/// <summary> Returns true if the internal QTextStream::isValid method returns QTextStream::Ok </summary>
bool isValid();
/// <summary> Write a '[' token </summary>
//! \return \p this if the write succeeded nullptr otherwise
JsonWriterNoState* sequence();
/// <summary> Write a '{' token </summary>
//! \return \p this if the write succeeded nullptr otherwise
JsonWriterNoState* record();
/// <summary> Write the \p p_numb item </summary>
//! \return False if \p p_numb cannot be write and true otherwise
bool bindNumber(qlonglong& p_numb);
/// <summary> Write the \p p_numb item </summary>
//! \return False if \p p_numb cannot be write and true otherwise
bool bindNumber(double& p_numb);
/// <summary> Write the \p p_bool item </summary>
//! \return False if \p p_bool cannot be write and true otherwise
bool bindBoolean(bool& p_bool);
/// <summary> Write the \p p_datetime item </summary>
//! \return False if \p p_datetime cannot be write and true otherwise
bool bindTimeStamp(QDateTime& p_datetime);
/// <summary> Write the \p p_bytes item </summary>
//! \return False if \p p_bytes cannot be write and true otherwise
bool bindBytes(QByteArray& p_bytes);
/// <summary> Write \p p_txt </summary>
//! \return true if the write succeeded, false otherwise
bool text(const QString& p_txt);
/// <summary> Write \p p_dat </summary>
//! \return true if the write succeeded, false otherwise
bool bindText(QString& p_dat);
/// <summary> Write a '\c null' token </summary>
//! \return true if the write succeeded false otherwise
bool null();
/// <summary> Write a ']' or '}' token </summary>
//! \return true if the write succeeded false otherwise
bool out();
/// <summary> Write a ',' token if it is not the first item </summary>
//! \return \p this if the write succeeded nullptr otherwise
JsonWriterNoState* item();
/// <summary> Write a ',' token if it is not the first item followed by \p p_key and the ':' token </summary>
//! \return \p this if the write succeeded nullptr otherwise
JsonWriterNoState* item(const QString& p_key);
/// <summary> Writes document information at the beginning of the stream </summary>
//! \warning Does nothing if the underlying stream position is not 0
//! \remark isDocument() allows checking that document information was written
//!
//! \pre !information.isNull()
//! \pre !m_isDocument
//! \pre m_stream.pos() == 0
//!
//! JSON document information is added as { "document":information, "data":... } which means
//! it can consist in any well-formed JSON like:
//! "log file"
//!
JsonWriterNoState& document(const QString& information = QString("\"\""));
/// <summary> Returns true if a call to #document succeeded </summary>
bool isDocument();
template<typename T>
void bind(T& d)
{ BindDispatchNoState<JsonWriterNoState, T>::bind(Itm<JsonWriterNoState>(this, *this), d); }
template<typename T>
void bind (const T& d)
{ bind(const_cast<T&>(d)); }
template<typename T, unsigned Size>
void bind (const T(&d)[Size])
{ bind(const_cast<T(&)[Size]>(d)); }
Itm<JsonWriterNoState> write();
static const Operator OperatorValue = Writer;
using TResult = JsonWriterNoState;
using TItem = JsonWriterNoState*;
using TSequence = JsonWriterNoState*;
using TRecord = JsonWriterNoState*;
private:
QSharedPointer<JsonWriterNoStatePrivate> m_d;
};
} // data
} // modmed
......@@ -47,7 +47,8 @@ HEADERS += \
include/modmed/log/LogManager.h \
include/modmed/log/LogWriter.h \
include/modmed/log/Log.h \
source/data/Cbor.h
source/data/Cbor.h \
include/modmed/data/JsonWriterNoState.h
unix {
HEADERS += \
......@@ -78,7 +79,8 @@ SOURCES += \
source/log/TsvJsonLogWriter.cpp \
source/log/LogEventData.cpp \
source/data/CborWriter.cpp \
source/log/LogManager.cpp
source/log/LogManager.cpp \
source/data/JsonWriterNoState.cpp
unix {
SOURCES += \
......
......@@ -7,6 +7,7 @@
#include <modmed/data/Bind.h>
#include <modmed/data/JsonWriter.h>
#include <modmed/data/JsonWriterNoState.h>
#include <modmed/data/XmlWriter.h>
#include <modmed/data/CborWriter.h>
......@@ -147,6 +148,18 @@ int main()
;
}
STOP("Cbor",b.buffer().toHex());
START {
b.seek(0); b.buffer().clear();
data::JsonWriterNoState data(&b, QString(), 0, '\0');
data.write().sequence()
.bind(true)
.bind(2)
.bind(1.23f)
.bind(text)
.out()
;
}
STOP("JsonNoState",QString::fromUtf8(b.buffer()));
}
GROUP_STOP;
GROUP("Format << map")
......@@ -182,6 +195,14 @@ int main()
;
}
STOP("Cbor",b.buffer().toHex());
START {
b.seek(0); b.buffer().clear();
data::JsonWriterNoState data(&b, QString(), 0, '\0');
data.write()
.bind(map)
;
}
STOP("JsonNoState",QString::fromUtf8(b.buffer()));
}
GROUP_STOP;
GROUP("Format << floats")
......@@ -216,6 +237,13 @@ int main()
.bind(transform);
}
STOP("Cbor",b.buffer().toHex())
START {
b.seek(0); b.buffer().clear();
data::JsonWriterNoState data(&b, QString(), 0, '\0');
data.write().sequence()
.bind(transform);
}
STOP("JsonNoState",b.buffer())
}
GROUP_STOP
GROUP("LogMessage << literal")
......
/****************************************************************************
* **
* ** Copyright (C) 2016 MinMaxMedical.
* ** All rights reserved.
* ** Contact: MinMaxMedical <InCAS@MinMaxMedical.com>
* **
* ** This file is part of the modmedLog module.
* **
* ** $QT_BEGIN_LICENSE:BSD$
* ** You may use this file under the terms of the BSD license as follows:
* **
* ** "Redistribution and use in source and binary forms, with or without
* ** modification, are permitted provided that the following conditions are
* ** met:
* ** * Redistributions of source code must retain the above copyright
* ** notice, this list of conditions and the following disclaimer.
* ** * Redistributions in binary form must reproduce the above copyright
* ** notice, this list of conditions and the following disclaimer in
* ** the documentation and/or other materials provided with the
* ** distribution.
* ** * Neither the name of MinMaxMedical S.A.S. and its Subsidiary(-ies) nor
* ** the names of its contributors may be used to endorse or promote
* ** products derived from this software without specific prior written
* ** permission.
* **
* ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
* ** $QT_END_LICENSE$
* **
* ****************************************************************************/
#include <QtCore/qbytearray.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qstack.h>
#include <QtCore/qstring.h>
#include <QtCore/qtextcodec.h>
#include <QtCore/qtextstream.h>
#include <modmed/data/JsonWriterNoState.h>
namespace modmed {
namespace data {
enum _Container
{
JsonRec, JsonSeq, JsonItm
};
struct JsonWriterNoStatePrivate
{
bool m_isDocument;
QTextStream* m_stream;
unsigned short m_maxIndentLevel;
char m_maxIndentLevelSeparator;
bool m_ioIsGood = true;
int m_itemInCurrentContainer = 0;
QTextStream internalStream;
QStack<_Container> m_containerStack;
JsonWriterNoStatePrivate(bool p_isDocument, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator, QTextStream* p_s) :
m_isDocument(p_isDocument),
m_maxIndentLevel(p_maxIndentLevel),
m_maxIndentLevelSeparator(p_maxIndentLevelSeparator)
{
m_stream = p_s;
}
JsonWriterNoStatePrivate(bool p_isDocument, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator, QIODevice* p_io) :
m_isDocument(p_isDocument),
m_maxIndentLevel(p_maxIndentLevel),
m_maxIndentLevelSeparator(p_maxIndentLevelSeparator),
internalStream(p_io)
{
m_stream = &internalStream;
m_stream->setCodec("UTF-8");
}
JsonWriterNoStatePrivate(bool p_isDocument, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator, FILE* p_f) :
m_isDocument(p_isDocument),
m_maxIndentLevel(p_maxIndentLevel),
m_maxIndentLevelSeparator(p_maxIndentLevelSeparator),
internalStream(p_f)
{
m_stream = &internalStream;
m_stream->setCodec("UTF-8");
}
bool message();
bool messageOut();
JsonWriterNoStatePrivate(bool p_isDocument, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator, QString* p_s) :
m_isDocument(p_isDocument),
m_maxIndentLevel(p_maxIndentLevel),
m_maxIndentLevelSeparator(p_maxIndentLevelSeparator),
internalStream(p_s)
{
m_stream = &internalStream;
m_stream->setCodec("UTF-8");
}
void escape(const QString& s);
void indent(bool isItem = false );
void end ();
};
//! \see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
void JsonWriterNoStatePrivate::escape(const QString& s)
{
Q_ASSERT(m_stream);
JsonWriterNoState::escape(*m_stream, s);
}
void JsonWriterNoStatePrivate::indent(bool isItem)
{
if (0 < m_maxIndentLevel)
{
auto level = m_containerStack.size();
if (level < m_maxIndentLevel) {
*m_stream << endl;
for (unsigned i = 0; i < level ; ++i)
*m_stream << ' ';
}
}
if (isItem && m_maxIndentLevelSeparator != '\0' && m_containerStack.size() == m_maxIndentLevel)
{
*m_stream << m_maxIndentLevelSeparator;
}
}
void JsonWriterNoStatePrivate::end()
{
if (!m_containerStack.isEmpty() && m_isDocument)
{
*m_stream << endl;
*m_stream << '}';
}
}
//==============================================================================
JsonWriterNoState::JsonWriterNoState(QTextStream* s, const QString& documentInformation /*= QString()*/, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator):
m_d(new JsonWriterNoStatePrivate(false, p_maxIndentLevel, p_maxIndentLevelSeparator, s))
{
if (!documentInformation.isNull()) { document(documentInformation); }
}
//==============================================================================
JsonWriterNoState::JsonWriterNoState(QIODevice* io, const QString& documentInformation /*= QString()*/, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator):
m_d(new JsonWriterNoStatePrivate(false, p_maxIndentLevel, p_maxIndentLevelSeparator, io))
{
if (!documentInformation.isNull()) { document(documentInformation); }
}
//==============================================================================
JsonWriterNoState::JsonWriterNoState(FILE* f, const QString& documentInformation /*= QString()*/, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator):
m_d(new JsonWriterNoStatePrivate(false, p_maxIndentLevel, p_maxIndentLevelSeparator, f))
{
if (!documentInformation.isNull()) { document(documentInformation); }
}
//==============================================================================
JsonWriterNoState::JsonWriterNoState(QString* s, const QString& documentInformation /*= QString()*/, unsigned short p_maxIndentLevel, char p_maxIndentLevelSeparator):
m_d(new JsonWriterNoStatePrivate(false, p_maxIndentLevel, p_maxIndentLevelSeparator, s))
{
if (!documentInformation.isNull()) { document(documentInformation); }
}
//==============================================================================
JsonWriterNoState::~JsonWriterNoState() {}
//==============================================================================
JsonWriterNoState& JsonWriterNoState::document(const QString& information /*= QString("\"\"")*/)
{
Q_ASSERT(!information.isNull());
Q_ASSERT(!m_d->m_isDocument);
Q_ASSERT(m_d->m_stream->pos() == 0);
*m_d->m_stream << "{";
if (!information.isEmpty())
{
*m_d->m_stream << "\"document\":" << information << ",";
}
*m_d->m_stream << "\"data\":" << endl;
m_d->m_isDocument = true;
return *this;
}
//==============================================================================
bool JsonWriterNoState::isDocument() { return m_d->m_isDocument; }
//==============================================================================
void JsonWriterNoState::flush() { m_d->m_stream->flush(); }
//==============================================================================
JsonWriterNoState* JsonWriterNoState::sequence()