#include "ext/powerblog/h2o-pp.hh" #include "minicurl.hh" #include #include "navmon.hh" #include "fmt/format.h" #include "fmt/printf.h" #include "galileo.hh" #include "gps.hh" #include "CLI/CLI.hpp" #include "version.hh" #include "ephemeris.hh" #include "influxpush.hh" #include "sp3.hh" static char program[]="reporter"; using namespace std; extern const char* g_gitHash; class Stats { public: void add(double v) { results.dirty=true; results.d.push_back(v); } struct Results { vector d; mutable bool dirty{false}; double median() const { return quantile(0.5); } double quantile(double p) const { unsigned int pos = p * d.size(); if(!d.size()) throw runtime_error("Empty range for quantile"); if(pos == d.size()) --pos; return d[p*d.size()]; } }; const Results& done() const { if(results.dirty) { sort(results.d.begin(), results.d.end()); results.dirty=false; } return results; } bool empty() const { return results.d.empty(); } private: mutable Results results; }; /* Goal: generate statistics from influxdb. Method: per x minute interval, determine status for all SVs. We only count minutes in which we have data We do only positive measurements, and report absence of data in neutral terms - either there was no reception or no transmission, we don't know. If an interval has no data: unobserved If interval has any data, it counts as observed If interval has a single unhealthy status, it is entirely unhealthy If interval is NAPA, but otherwise healthy, status is NAPA If observed and nothing else, status is healthy Input: time range, width if interval Internal: per SV, interval, bitfield Output: per SV, number of intervals healthy, number of intervals NAPA, number of intervals unhealthy, number of intervals unobserved */ struct IntervalStat { std::optional unhealthy; std::optional dataunhealthy; std::optional osnma; std::optional sisa; bool ripe{false}; bool expired{false}; double rtcmDist{-1}; std::optional rtcmDClock; }; map> g_stats; int main(int argc, char **argv) try { MiniCurl mc; MiniCurl::MiniCurlHeaders mch; string influxDBName("galileo"); string period="time > now() - 1w"; int sigid=1; bool doVERSION{false}; CLI::App app(program); string periodarg("1d"); string beginarg, endarg; string sp3src("default"); int gnssid=2; int rtcmsrc=300; int galwn=-1; string influxserver="http://127.0.0.1:8086"; app.add_flag("--version", doVERSION, "show program version and copyright"); app.add_option("--period,-p", periodarg, "period over which to report (1h, 1w)"); app.add_option("--begin,-b", beginarg, "Beginning"); app.add_option("--end,-e", endarg, "End"); app.add_option("--gal-wn", galwn, "Galileo week number to report on"); app.add_option("--sp3src", sp3src, "Identifier of SP3 source"); app.add_option("--rtcmsrc", rtcmsrc, "Identifier of RTCM source"); app.add_option("--sigid,-s", sigid, "Signal identifier. 1 or 5 for Galileo."); app.add_option("--gnssid,-g", gnssid, "gnssid, 0 GPS, 2 Galileo"); app.add_option("--influxdb", influxDBName, "Name of influxdb database"); app.add_option("--influxserver", influxserver, "Address of influx server"); try { app.parse(argc, argv); } catch(const CLI::Error &e) { return app.exit(e); } if(doVERSION) { showVersion(program, g_gitHash); exit(0); } if(galwn>= 0) { time_t w = utcFromGST(galwn, 0); period = "time >= '"+influxTime(w)+"' and time < '"+influxTime(w+7*86400) +"'"; } else if(beginarg.empty() && endarg.empty()) period = "time > now() - "+periodarg; else { period = "time > '"+ beginarg +"' and time <= '" + endarg +"'"; cout<<"Period: "< 1 && v[1] != nullptr) { int seconds = (int)v[1]; if(seconds > 86400) { // probably wraparound } else if(seconds > 4*3600) { g_stats[id][(int)v[0]].expired = 1; cout< 2*3600) g_stats[id][(int)v[0]].ripe = (int)v[1] > 7200; } } } ///////////////////// rtcm-eph string rtcmQuery = "select mean(\"radial\") from \"rtcm-eph-correction\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and src='"+to_string(rtcmsrc)+"' group by gnssid,sv,sigid,time(10m)"; cout<<"rtcmquery: "<> galephs; map> gpsephs; res = mc.getURL(url + mc.urlEncode("select * from \"ephemeris-actual\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' group by gnssid,sv,sigid")); j = nlohmann::json::parse(res); for(const auto& sv : j["results"][0]["series"]) { try { const auto& tags=sv["tags"]; SatID id{(unsigned int)std::stoi((string)tags["gnssid"]), (unsigned int)std::stoi((string)tags["sv"]), (unsigned int)std::stoi((string)tags["sigid"])}; // cout << makeSatIDName(id) <<": "< cmap; for(const auto& c : sv["columns"]) { cmap[c]=cmap.size(); } for(const auto& v : sv["values"]) { // cout << makeSatIDName(id)<<": crc "<> sp3s; string spq3="select * from \"sp3\" where "+period+" and sp3src =~ /"+sp3src+"/ and gnssid='"+to_string(gnssid)+"' group by gnssid,sv,sigid"; cout<<"sp3 query: "< cmap; for(const auto& c : sv["columns"]) { cmap[c]=cmap.size(); } for(const auto& v : sv["values"]) { // cout << makeSatIDName(id)<<": time "< sp3zerrors, sp3clockerrors; ///////////////////// for(const auto& svsp3 : sp3s) { const auto& id = svsp3.first; const auto& es = svsp3.second; // cout<<"Looking at SP3 for " << makeSatIDName(id)<<", have "<first)<<" iod "<second.iodnav; // our UTC timestamp from SP3 need to be converted into a tow int offset = id.gnss ? 935280000 : 315964800; int sp3tow = (e.first - offset) % (7*86400); Point epos; getCoordinates(sp3tow, iter->second, &epos); double clkoffset= iter->second.getAtomicOffset(sp3tow).first - e.second.clockBias; // cout<<" "<second.getAtomicOffset(sp3tow).first <<" v " << e.second.clockBias<<" ns "; // cout << " ("< ("< " << v.length(); Vector dir(Point(0,0,0), sp3pos); dir.norm(); Point cv=sp3pos; cv.x -= 0.519 * dir.x; cv.y -= 0.519 * dir.y; cv.z -= 0.519 * dir.z; Vector v2(epos, cv); // cout<< " -> " << v2.length(); // cout<<" z-error: "<second.iodnav}}, e.first); sp3clockerrors[id].add(clkoffset); // nanoseconds sp3zerrors[id].add(100*dir.inner(v) - 80); // meters -> cm } } else if(id.gnss==0) { const auto& svephs = gpsephs[id]; // this is keyed on the moment of _issue_ auto iter = svephs.lower_bound(e.first); if(iter != svephs.end() && iter != svephs.begin()) { --iter; // cout << "found ephemeris from "<< humanTimeShort(iter->first)<<" iod "<second.iodnav; // our UTC timestamp from SP3 need to be converted into a tow int offset = 315964800; int sp3tow = (e.first - offset) % (7*86400); Point epos; getCoordinates(sp3tow, iter->second, &epos); double clkoffset= getGPSAtomicOffset(sp3tow, iter->second).first - e.second.clockBias; // cout<<" "<second.getAtomicOffset(sp3tow).first <<" v " << e.second.clockBias<<" ns "; // cout << " ("< ("< " << v.length(); Vector dir(Point(0,0,0), sp3pos); dir.norm(); Point cv=sp3pos; cv.x -= 0.519 * dir.x; cv.y -= 0.519 * dir.y; cv.z -= 0.519 * dir.z; Vector v2(epos, cv); // cout<< " -> " << v2.length(); // cout<<" z-error: "<second.gpsiod}}, e.first); sp3clockerrors[id].add(clkoffset); // nanoseconds sp3zerrors[id].add(100*dir.inner(v)); // meters -> cm } } } } ///// string dishesQuery = "select iod,sv from \"ephemeris-actual\" where "+period+" and sigid='"+to_string(sigid)+"' and gnssid='"+to_string(gnssid)+"' and iod < 128"; cout<<"dishesquery: "<> dishcount; set totsvs; for(const auto& sv : j["results"][0]["series"]) { for(const auto& v : sv["values"]) { try { int sv = (unsigned int)std::stoi((string)v[2]); int t = (int)v[0]; // t &= (~31); dishcount[t].insert(sv); totsvs.insert(sv); } catch(exception& e) { cerr<<"error: "< maxcounts; for(const auto& dc : dishcount) { auto& bin = maxcounts[dc.first - (dc.first % 3600)]; if(bin < dc.second.size()) bin = dc.second.size(); cout << dc.first<<" "<first < start) start = sv.second.begin()->first; if(sv.second.rbegin()->first > stop) stop = sv.second.rbegin()->first; } unsigned int liveInterval=0; for(const auto i : sv.second) { if(i.second.sisa.has_value()) liveInterval++; else { // cout< maxintervals) maxintervals = liveInterval; } cout<<"Report on "<= 0) { rtcm.add(i.second.rtcmDist); totRTCM.add(i.second.rtcmDist); } if(i.second.rtcmDClock) clockRtcm.add(*i.second.rtcmDClock * 100); if(i.second.ripe) ripe++; if(i.second.expired) expired++; if(i.second.unhealthy) { if(*i.second.unhealthy==1) unhealthy++; else if(*i.second.unhealthy==3) testing++; else { if(i.second.dataunhealthy && *i.second.dataunhealthy) { // this is 'working without guarantee' unhealthy++; } else if(i.second.sisa) { if(*i.second.sisa == 255) napa++; else healthy++; } else healthy++; } } else if(i.second.sisa) { if(*i.second.sisa == 255) napa++; } } // cout<