From 563daa96a873ef627ca42d24e0f6de6bb359326d Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Mon, 25 Jan 2010 12:07:25 -0800 Subject: pull in the linux+solaris fast-timers impl from snowglobe, fit it into viewer2, start moving headers around. --- indra/llcommon/llfasttimer.h | 364 ------------------------------------------- 1 file changed, 364 deletions(-) delete mode 100644 indra/llcommon/llfasttimer.h (limited to 'indra/llcommon/llfasttimer.h') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h deleted file mode 100644 index 8af79c90fd..0000000000 --- a/indra/llcommon/llfasttimer.h +++ /dev/null @@ -1,364 +0,0 @@ -/** - * @file llfasttimer.h - * @brief Declaration of a fast timer. - * - * $LicenseInfo:firstyear=2004&license=viewergpl$ - * - * Copyright (c) 2004-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#ifndef LL_FASTTIMER_H -#define LL_FASTTIMER_H - -#include "llinstancetracker.h" - -#define FAST_TIMER_ON 1 -#define TIME_FAST_TIMERS 0 - -#if LL_WINDOWS -#define LL_INLINE __forceinline - -// -// NOTE: put back in when we aren't using platform sdk anymore -// -// because MS has different signatures for these functions in winnt.h -// need to rename them to avoid conflicts -//#define _interlockedbittestandset _renamed_interlockedbittestandset -//#define _interlockedbittestandreset _renamed_interlockedbittestandreset -//#include -//#undef _interlockedbittestandset -//#undef _interlockedbittestandreset - -//inline U32 get_cpu_clock_count_32() -//{ -// U64 time_stamp = __rdtsc(); -// return (U32)(time_stamp >> 8); -//} -// -//// return full timer value, *not* shifted by 8 bits -//inline U64 get_cpu_clock_count_64() -//{ -// return __rdtsc(); -//} - -// shift off lower 8 bits for lower resolution but longer term timing -// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing -inline U32 get_cpu_clock_count_32() -{ - U32 ret_val; - __asm - { - _emit 0x0f - _emit 0x31 - shr eax,8 - shl edx,24 - or eax, edx - mov dword ptr [ret_val], eax - } - return ret_val; -} - -// return full timer value, *not* shifted by 8 bits -inline U64 get_cpu_clock_count_64() -{ - U64 ret_val; - __asm - { - _emit 0x0f - _emit 0x31 - mov eax,eax - mov edx,edx - mov dword ptr [ret_val+4], edx - mov dword ptr [ret_val], eax - } - return ret_val; -} -#else -#define LL_INLINE -#endif - -#if (LL_LINUX || LL_SOLARIS || LL_DARWIN) && (defined(__i386__) || defined(__amd64__)) -inline U32 get_cpu_clock_count_32() -{ - U64 x; - __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); - return (U32)x >> 8; -} - -inline U32 get_cpu_clock_count_64() -{ - U64 x; - __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); - return x >> 8; -} -#endif - -#if ( LL_DARWIN && !(defined(__i386__) || defined(__amd64__))) || (LL_SOLARIS && defined(__sparc__)) -// -// Mac PPC (deprecated) & Solaris SPARC implementation of CPU clock -// -// Just use gettimeofday implementation for now - -inline U32 get_cpu_clock_count_32() -{ - return (U32)get_clock_count(); -} - -inline U32 get_cpu_clock_count_64() -{ - return get_clock_count(); -} -#endif - -class LLMutex; - -#include -#include "llsd.h" - -class LL_COMMON_API LLFastTimer -{ -public: - - class NamedTimer; - - struct LL_COMMON_API FrameState - { - FrameState(NamedTimer* timerp); - - U32 mSelfTimeCounter; - U32 mCalls; - FrameState* mParent; // info for caller timer - FrameState* mLastCaller; // used to bootstrap tree construction - NamedTimer* mTimer; - U16 mActiveCount; // number of timers with this ID active on stack - bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame - }; - - // stores a "named" timer instance to be reused via multiple LLFastTimer stack instances - class LL_COMMON_API NamedTimer - : public LLInstanceTracker - { - friend class DeclareTimer; - public: - ~NamedTimer(); - - enum { HISTORY_NUM = 60 }; - - const std::string& getName() const { return mName; } - NamedTimer* getParent() const { return mParent; } - void setParent(NamedTimer* parent); - S32 getDepth(); - std::string getToolTip(S32 history_index = -1); - - typedef std::vector::const_iterator child_const_iter; - child_const_iter beginChildren(); - child_const_iter endChildren(); - std::vector& getChildren(); - - void setCollapsed(bool collapsed) { mCollapsed = collapsed; } - bool getCollapsed() const { return mCollapsed; } - - U32 getCountAverage() const { return mCountAverage; } - U32 getCallAverage() const { return mCallAverage; } - - U32 getHistoricalCount(S32 history_index = 0) const; - U32 getHistoricalCalls(S32 history_index = 0) const; - - static NamedTimer& getRootNamedTimer(); - - S32 getFrameStateIndex() const { return mFrameStateIndex; } - - FrameState& getFrameState() const; - - private: - friend class LLFastTimer; - friend class NamedTimerFactory; - - // - // methods - // - NamedTimer(const std::string& name); - // recursive call to gather total time from children - static void accumulateTimings(); - - // updates cumulative times and hierarchy, - // can be called multiple times in a frame, at any point - static void processTimes(); - - static void buildHierarchy(); - static void resetFrame(); - static void reset(); - - // - // members - // - S32 mFrameStateIndex; - - std::string mName; - - U32 mTotalTimeCounter; - - U32 mCountAverage; - U32 mCallAverage; - - U32* mCountHistory; - U32* mCallHistory; - - // tree structure - NamedTimer* mParent; // NamedTimer of caller(parent) - std::vector mChildren; - bool mCollapsed; // don't show children - bool mNeedsSorting; // sort children whenever child added - }; - - // used to statically declare a new named timer - class LL_COMMON_API DeclareTimer - : public LLInstanceTracker - { - friend class LLFastTimer; - public: - DeclareTimer(const std::string& name, bool open); - DeclareTimer(const std::string& name); - - static void updateCachedPointers(); - - private: - NamedTimer& mTimer; - FrameState* mFrameState; - }; - -public: - LLFastTimer(LLFastTimer::FrameState* state); - - LL_INLINE LLFastTimer(LLFastTimer::DeclareTimer& timer) - : mFrameState(timer.mFrameState) - { -#if TIME_FAST_TIMERS - U64 timer_start = get_cpu_clock_count_64(); -#endif -#if FAST_TIMER_ON - LLFastTimer::FrameState* frame_state = mFrameState; - mStartTime = get_cpu_clock_count_32(); - - frame_state->mActiveCount++; - frame_state->mCalls++; - // keep current parent as long as it is active when we are - frame_state->mMoveUpTree |= (frame_state->mParent->mActiveCount == 0); - - LLFastTimer::CurTimerData* cur_timer_data = &LLFastTimer::sCurTimerData; - mLastTimerData = *cur_timer_data; - cur_timer_data->mCurTimer = this; - cur_timer_data->mFrameState = frame_state; - cur_timer_data->mChildTime = 0; -#endif -#if TIME_FAST_TIMERS - U64 timer_end = get_cpu_clock_count_64(); - sTimerCycles += timer_end - timer_start; -#endif - } - - LL_INLINE ~LLFastTimer() - { -#if TIME_FAST_TIMERS - U64 timer_start = get_cpu_clock_count_64(); -#endif -#if FAST_TIMER_ON - LLFastTimer::FrameState* frame_state = mFrameState; - U32 total_time = get_cpu_clock_count_32() - mStartTime; - - frame_state->mSelfTimeCounter += total_time - LLFastTimer::sCurTimerData.mChildTime; - frame_state->mActiveCount--; - - // store last caller to bootstrap tree creation - // do this in the destructor in case of recursion to get topmost caller - frame_state->mLastCaller = mLastTimerData.mFrameState; - - // we are only tracking self time, so subtract our total time delta from parents - mLastTimerData.mChildTime += total_time; - - LLFastTimer::sCurTimerData = mLastTimerData; -#endif -#if TIME_FAST_TIMERS - U64 timer_end = get_cpu_clock_count_64(); - sTimerCycles += timer_end - timer_start; - sTimerCalls++; -#endif - } - -public: - static LLMutex* sLogLock; - static std::queue sLogQueue; - static BOOL sLog; - static BOOL sMetricLog; - static bool sPauseHistory; - static bool sResetHistory; - static U64 sTimerCycles; - static U32 sTimerCalls; - - typedef std::vector info_list_t; - static info_list_t& getFrameStateList(); - - - // call this once a frame to reset timers - static void nextFrame(); - - // dumps current cumulative frame stats to log - // call nextFrame() to reset timers - static void dumpCurTimes(); - - // call this to reset timer hierarchy, averages, etc. - static void reset(); - - static U64 countsPerSecond(); - static S32 getLastFrameIndex() { return sLastFrameIndex; } - static S32 getCurFrameIndex() { return sCurFrameIndex; } - - static void writeLog(std::ostream& os); - static const NamedTimer* getTimerByName(const std::string& name); - - struct CurTimerData - { - LLFastTimer* mCurTimer; - FrameState* mFrameState; - U32 mChildTime; - }; - static CurTimerData sCurTimerData; - -private: - static S32 sCurFrameIndex; - static S32 sLastFrameIndex; - static U64 sLastFrameTime; - static info_list_t* sTimerInfos; - - U32 mStartTime; - LLFastTimer::FrameState* mFrameState; - LLFastTimer::CurTimerData mLastTimerData; - -}; - -typedef class LLFastTimer LLFastTimer; - -#endif // LL_LLFASTTIMER_H -- cgit v1.2.3 From ec7b204ed657f7c1becac3b410ae0bc94c03aa75 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Mon, 25 Jan 2010 12:31:50 -0800 Subject: shuffle shuffle of timer code. cleanup. --- indra/llcommon/llfasttimer.h | 169 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 indra/llcommon/llfasttimer.h (limited to 'indra/llcommon/llfasttimer.h') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h new file mode 100644 index 0000000000..0f3280023c --- /dev/null +++ b/indra/llcommon/llfasttimer.h @@ -0,0 +1,169 @@ +/** + * @file llfasttimer.h + * @brief Inline implementations of fast timers. + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_FASTTIMER_H +#define LL_FASTTIMER_H + +// pull in the actual class definition +#include "llfasttimer_class.h" + +#if LL_WINDOWS +#define LL_INLINE __forceinline +// +// Windows implementation of CPU clock +// + +// +// NOTE: put back in when we aren't using platform sdk anymore +// +// because MS has different signatures for these functions in winnt.h +// need to rename them to avoid conflicts +//#define _interlockedbittestandset _renamed_interlockedbittestandset +//#define _interlockedbittestandreset _renamed_interlockedbittestandreset +//#include +//#undef _interlockedbittestandset +//#undef _interlockedbittestandreset + +//inline U32 get_cpu_clock_count_32() +//{ +// U64 time_stamp = __rdtsc(); +// return (U32)(time_stamp >> 8); +//} +// +//// return full timer value, *not* shifted by 8 bits +//inline U64 get_cpu_clock_count_64() +//{ +// return __rdtsc(); +//} + +// shift off lower 8 bits for lower resolution but longer term timing +// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing +inline U32 get_cpu_clock_count_32() +{ + U32 ret_val; + __asm + { + _emit 0x0f + _emit 0x31 + shr eax,8 + shl edx,24 + or eax, edx + mov dword ptr [ret_val], eax + } + return ret_val; +} + +// return full timer value, *not* shifted by 8 bits +inline U64 get_cpu_clock_count_64() +{ + U64 ret_val; + __asm + { + _emit 0x0f + _emit 0x31 + mov eax,eax + mov edx,edx + mov dword ptr [ret_val+4], edx + mov dword ptr [ret_val], eax + } + return ret_val; +} +#else +#define LL_INLINE +#endif + + +#if LL_LINUX || LL_SOLARIS +// +// Linux and Solaris implementation of CPU clock - all architectures. +// +// Try to use the MONOTONIC clock if available, this is a constant time counter +// with nanosecond resolution (but not necessarily accuracy) and attempts are made +// to synchronize this value between cores at kernel start. It should not be affected +// by CPU frequency. If not available use the REALTIME clock, but this may be affected by +// NTP adjustments or other user activity affecting the system time. +inline U64 get_cpu_clock_count_64() +{ + struct timespec tp; + +#ifdef CLOCK_MONOTONIC // MONOTONIC supported at build-time? + if (-1 == clock_gettime(CLOCK_MONOTONIC,&tp)) // if MONOTONIC isn't supported at runtime, try REALTIME +#endif + clock_gettime(CLOCK_REALTIME,&tp); + + return (tp.tv_sec*LLFastTimer::sClockResolution)+tp.tv_nsec; +} + +inline U32 get_cpu_clock_count_32() +{ + return (U32)get_cpu_clock_count_64(); +} +#endif // (LL_LINUX || LL_SOLARIS)) + + +#if (LL_DARWIN) && (defined(__i386__) || defined(__amd64__)) +// +// Mac x86 implementation of CPU clock +inline U32 get_cpu_clock_count_32() +{ + U64 x; + __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); + return (U32)x >> 8; +} + +inline U32 get_cpu_clock_count_64() +{ + U64 x; + __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); + return x >> 8; +} +#endif + + +#if ( LL_DARWIN && !(defined(__i386__) || defined(__amd64__))) +// +// Mac PPC (deprecated) implementation of CPU clock +// +// Just use gettimeofday implementation for now + +inline U32 get_cpu_clock_count_32() +{ + return (U32)get_clock_count(); +} + +inline U32 get_cpu_clock_count_64() +{ + return get_clock_count(); +} +#endif + +#endif // LL_LLFASTTIMER_H -- cgit v1.2.3 From 3d0ff2585eb32c67d503452ce9f8d2198be823c8 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Mon, 25 Jan 2010 13:24:45 -0800 Subject: Final fix for fast timer reshuffle. --- indra/llcommon/llfasttimer.h | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'indra/llcommon/llfasttimer.h') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 0f3280023c..9f9e2ea945 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -37,7 +37,6 @@ #include "llfasttimer_class.h" #if LL_WINDOWS -#define LL_INLINE __forceinline // // Windows implementation of CPU clock // @@ -53,21 +52,21 @@ //#undef _interlockedbittestandset //#undef _interlockedbittestandreset -//inline U32 get_cpu_clock_count_32() +//inline U32 LLFastTimer::getCPUClockCount32() //{ // U64 time_stamp = __rdtsc(); // return (U32)(time_stamp >> 8); //} // //// return full timer value, *not* shifted by 8 bits -//inline U64 get_cpu_clock_count_64() +//inline U64 LLFastTimer::getCPUClockCount64() //{ // return __rdtsc(); //} // shift off lower 8 bits for lower resolution but longer term timing // on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing -inline U32 get_cpu_clock_count_32() +inline U32 LLFastTimer::getCPUClockCount32() { U32 ret_val; __asm @@ -83,7 +82,7 @@ inline U32 get_cpu_clock_count_32() } // return full timer value, *not* shifted by 8 bits -inline U64 get_cpu_clock_count_64() +inline U64 LLFastTimer::getCPUClockCount64() { U64 ret_val; __asm @@ -97,8 +96,6 @@ inline U64 get_cpu_clock_count_64() } return ret_val; } -#else -#define LL_INLINE #endif @@ -111,7 +108,7 @@ inline U64 get_cpu_clock_count_64() // to synchronize this value between cores at kernel start. It should not be affected // by CPU frequency. If not available use the REALTIME clock, but this may be affected by // NTP adjustments or other user activity affecting the system time. -inline U64 get_cpu_clock_count_64() +inline U64 LLFastTimer::getCPUClockCount64() { struct timespec tp; @@ -123,9 +120,9 @@ inline U64 get_cpu_clock_count_64() return (tp.tv_sec*LLFastTimer::sClockResolution)+tp.tv_nsec; } -inline U32 get_cpu_clock_count_32() +inline U32 LLFastTimer::getCPUClockCount32() { - return (U32)get_cpu_clock_count_64(); + return (U32)LLFastTimer::getCPUClockCount64(); } #endif // (LL_LINUX || LL_SOLARIS)) @@ -133,14 +130,14 @@ inline U32 get_cpu_clock_count_32() #if (LL_DARWIN) && (defined(__i386__) || defined(__amd64__)) // // Mac x86 implementation of CPU clock -inline U32 get_cpu_clock_count_32() +inline U32 LLFastTimer::getCPUClockCount32() { U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); return (U32)x >> 8; } -inline U32 get_cpu_clock_count_64() +inline U32 LLFastTimer::getCPUClockCount64() { U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); @@ -155,12 +152,12 @@ inline U32 get_cpu_clock_count_64() // // Just use gettimeofday implementation for now -inline U32 get_cpu_clock_count_32() +inline U32 LLFastTimer::getCPUClockCount32() { return (U32)get_clock_count(); } -inline U32 get_cpu_clock_count_64() +inline U32 LLFastTimer::getCPUClockCount64() { return get_clock_count(); } -- cgit v1.2.3 From 2375afc4283e47a50516c1e003a6f699b0a2cfe1 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Mon, 25 Jan 2010 13:49:13 -0800 Subject: Gosh, the mac prototypes for get_cpu_clock_count_64 have always been wrong, but the compiler didn't start caring until I made these proper member functions. fixed. --- indra/llcommon/llfasttimer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/llfasttimer.h') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 9f9e2ea945..32f3561616 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -137,7 +137,7 @@ inline U32 LLFastTimer::getCPUClockCount32() return (U32)x >> 8; } -inline U32 LLFastTimer::getCPUClockCount64() +inline U64 LLFastTimer::getCPUClockCount64() { U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); @@ -157,7 +157,7 @@ inline U32 LLFastTimer::getCPUClockCount32() return (U32)get_clock_count(); } -inline U32 LLFastTimer::getCPUClockCount64() +inline U64 LLFastTimer::getCPUClockCount64() { return get_clock_count(); } -- cgit v1.2.3 From b77fe6dc724c162b3de194d1b3f362ac4c7f5021 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Tue, 26 Jan 2010 12:35:22 -0800 Subject: DEV-45468 SNOW-108: Fast timers are broken / badly-scaled on linux more reliable fix based on feedback from Richard. dicked with the Darwin results too since those seemed wrong based on the same feedback (also covered in test plan). --- indra/llcommon/llfasttimer.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/llfasttimer.h') diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 32f3561616..48461df6ae 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -113,7 +113,7 @@ inline U64 LLFastTimer::getCPUClockCount64() struct timespec tp; #ifdef CLOCK_MONOTONIC // MONOTONIC supported at build-time? - if (-1 == clock_gettime(CLOCK_MONOTONIC,&tp)) // if MONOTONIC isn't supported at runtime, try REALTIME + if (-1 == clock_gettime(CLOCK_MONOTONIC,&tp)) // if MONOTONIC isn't supported at runtime then ouch, try REALTIME #endif clock_gettime(CLOCK_REALTIME,&tp); @@ -122,7 +122,7 @@ inline U64 LLFastTimer::getCPUClockCount64() inline U32 LLFastTimer::getCPUClockCount32() { - return (U32)LLFastTimer::getCPUClockCount64(); + return (U32)(LLFastTimer::getCPUClockCount64() >> 8); } #endif // (LL_LINUX || LL_SOLARIS)) @@ -134,14 +134,14 @@ inline U32 LLFastTimer::getCPUClockCount32() { U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); - return (U32)x >> 8; + return (U32)(x >> 8); } inline U64 LLFastTimer::getCPUClockCount64() { U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); - return x >> 8; + return x; } #endif @@ -154,7 +154,7 @@ inline U64 LLFastTimer::getCPUClockCount64() inline U32 LLFastTimer::getCPUClockCount32() { - return (U32)get_clock_count(); + return (U32)(get_clock_count()>>8); } inline U64 LLFastTimer::getCPUClockCount64() -- cgit v1.2.3