Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(237)

Side by Side Diff: base/metrics/field_trial.cc

Issue 2862123002: Pass the GUID for the SharedMemoryHandle used by base::FieldTrialList. (Closed)
Patch Set: Comments from asvitkine. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/metrics/field_trial.h" 5 #include "base/metrics/field_trial.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/base_switches.h" 10 #include "base/base_switches.h"
11 #include "base/build_time.h" 11 #include "base/build_time.h"
12 #include "base/command_line.h" 12 #include "base/command_line.h"
13 #include "base/debug/activity_tracker.h" 13 #include "base/debug/activity_tracker.h"
14 #include "base/logging.h" 14 #include "base/logging.h"
15 #include "base/metrics/field_trial_param_associator.h" 15 #include "base/metrics/field_trial_param_associator.h"
16 #include "base/process/memory.h" 16 #include "base/process/memory.h"
17 #include "base/rand_util.h" 17 #include "base/rand_util.h"
18 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
19 #include "base/strings/string_util.h" 20 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h" 21 #include "base/strings/stringprintf.h"
21 #include "base/strings/utf_string_conversions.h" 22 #include "base/strings/utf_string_conversions.h"
22 #include "base/unguessable_token.h" 23 #include "base/unguessable_token.h"
23 24
24 // On POSIX, the fd is shared using the mapping in GlobalDescriptors. 25 // On POSIX, the fd is shared using the mapping in GlobalDescriptors.
25 #if defined(OS_POSIX) && !defined(OS_NACL) 26 #if defined(OS_POSIX) && !defined(OS_NACL)
26 #include "base/posix/global_descriptors.h" 27 #include "base/posix/global_descriptors.h"
27 #endif 28 #endif
28 29
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
201 cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features); 202 cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features);
202 203
203 std::string field_trial_states; 204 std::string field_trial_states;
204 FieldTrialList::AllStatesToString(&field_trial_states); 205 FieldTrialList::AllStatesToString(&field_trial_states);
205 if (!field_trial_states.empty()) { 206 if (!field_trial_states.empty()) {
206 cmd_line->AppendSwitchASCII(switches::kForceFieldTrials, 207 cmd_line->AppendSwitchASCII(switches::kForceFieldTrials,
207 field_trial_states); 208 field_trial_states);
208 } 209 }
209 } 210 }
210 211
211 #if defined(OS_WIN)
212 HANDLE CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
213 HANDLE src = allocator->shared_memory()->handle().GetHandle();
214 ProcessHandle process = GetCurrentProcess();
215 DWORD access = SECTION_MAP_READ | SECTION_QUERY;
216 HANDLE dst;
217 if (!::DuplicateHandle(process, src, process, &dst, access, true, 0))
218 return kInvalidPlatformFile;
219 return dst;
220 }
221 #endif
222
223 #if defined(OS_POSIX) && !defined(OS_NACL)
224 int CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
225 SharedMemoryHandle handle = allocator->shared_memory()->GetReadOnlyHandle();
226 return SharedMemory::GetFdFromSharedMemoryHandle(handle);
227 }
228 #endif
229
230 void OnOutOfMemory(size_t size) { 212 void OnOutOfMemory(size_t size) {
231 #if defined(OS_NACL) 213 #if defined(OS_NACL)
232 NOTREACHED(); 214 NOTREACHED();
233 #else 215 #else
234 TerminateBecauseOutOfMemory(size); 216 TerminateBecauseOutOfMemory(size);
235 #endif 217 #endif
236 } 218 }
237 219
238 } // namespace 220 } // namespace
239 221
(...skipping 528 matching lines...) Expand 10 before | Expand all | Expand 10 after
768 // processes are properly reported in crash reports. 750 // processes are properly reported in crash reports.
769 trial->group(); 751 trial->group();
770 } 752 }
771 } 753 }
772 return true; 754 return true;
773 } 755 }
774 756
775 // static 757 // static
776 void FieldTrialList::CreateTrialsFromCommandLine( 758 void FieldTrialList::CreateTrialsFromCommandLine(
777 const CommandLine& cmd_line, 759 const CommandLine& cmd_line,
778 const char* field_trial_handle_switch, 760 const char* field_trial_switch_value,
Alexei Svitkine (slow) 2017/05/05 22:37:50 Nit: You still have a sed error here :P
erikchen 2017/05/08 23:37:49 Done.
779 int fd_key) { 761 int fd_key) {
780 global_->create_trials_from_command_line_called_ = true; 762 global_->create_trials_from_command_line_called_ = true;
781 763
782 #if defined(OS_WIN) 764 #if defined(OS_WIN)
783 if (cmd_line.HasSwitch(field_trial_handle_switch)) { 765 if (cmd_line.HasSwitch(field_trial_switch_value)) {
784 std::string handle_switch = 766 std::string switch_value =
785 cmd_line.GetSwitchValueASCII(field_trial_handle_switch); 767 cmd_line.GetSwitchValueASCII(field_trial_switch_value);
786 bool result = CreateTrialsFromHandleSwitch(handle_switch); 768 bool result = CreateTrialsFromSwitchValue(switch_value);
787 DCHECK(result); 769 DCHECK(result);
788 } 770 }
789 #endif 771 #endif
790 772
791 #if defined(OS_POSIX) && !defined(OS_NACL) 773 #if defined(OS_POSIX) && !defined(OS_NACL)
792 // On POSIX, we check if the handle is valid by seeing if the browser process 774 // On POSIX, we check if the handle is valid by seeing if the browser process
793 // sent over the switch (we don't care about the value). Invalid handles 775 // sent over the switch (we don't care about the value). Invalid handles
794 // occur in some browser tests which don't initialize the allocator. 776 // occur in some browser tests which don't initialize the allocator.
795 if (cmd_line.HasSwitch(field_trial_handle_switch)) { 777 if (cmd_line.HasSwitch(field_trial_switch_value)) {
796 bool result = CreateTrialsFromDescriptor(fd_key); 778 std::string switch_value =
779 cmd_line.GetSwitchValueASCII(field_trial_switch_value);
780 bool result = CreateTrialsFromDescriptor(fd_key, switch_value);
797 DCHECK(result); 781 DCHECK(result);
798 } 782 }
799 #endif 783 #endif
800 784
801 if (cmd_line.HasSwitch(switches::kForceFieldTrials)) { 785 if (cmd_line.HasSwitch(switches::kForceFieldTrials)) {
802 bool result = FieldTrialList::CreateTrialsFromString( 786 bool result = FieldTrialList::CreateTrialsFromString(
803 cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials), 787 cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials),
804 std::set<std::string>()); 788 std::set<std::string>());
805 DCHECK(result); 789 DCHECK(result);
806 } 790 }
(...skipping 18 matching lines...) Expand all
825 } 809 }
826 810
827 #if defined(OS_WIN) 811 #if defined(OS_WIN)
828 // static 812 // static
829 void FieldTrialList::AppendFieldTrialHandleIfNeeded( 813 void FieldTrialList::AppendFieldTrialHandleIfNeeded(
830 HandlesToInheritVector* handles) { 814 HandlesToInheritVector* handles) {
831 if (!global_) 815 if (!global_)
832 return; 816 return;
833 if (kUseSharedMemoryForFieldTrials) { 817 if (kUseSharedMemoryForFieldTrials) {
834 InstantiateFieldTrialAllocatorIfNeeded(); 818 InstantiateFieldTrialAllocatorIfNeeded();
835 if (global_->readonly_allocator_handle_) 819 if (global_->readonly_allocator_handle_.IsValid())
836 handles->push_back(global_->readonly_allocator_handle_); 820 handles->push_back(global_->readonly_allocator_handle_.GetHandle());
837 } 821 }
838 } 822 }
839 #endif 823 #endif
840 824
841 #if defined(OS_POSIX) && !defined(OS_NACL) 825 #if defined(OS_POSIX) && !defined(OS_NACL)
842 // static 826 // static
843 int FieldTrialList::GetFieldTrialHandle() { 827 SharedMemoryHandle FieldTrialList::GetFieldTrialHandle() {
844 if (global_ && kUseSharedMemoryForFieldTrials) { 828 if (global_ && kUseSharedMemoryForFieldTrials) {
845 InstantiateFieldTrialAllocatorIfNeeded(); 829 InstantiateFieldTrialAllocatorIfNeeded();
846 // We check for an invalid handle where this gets called. 830 // We check for an invalid handle where this gets called.
847 return global_->readonly_allocator_handle_; 831 return global_->readonly_allocator_handle_;
848 } 832 }
849 return kInvalidPlatformFile; 833 return SharedMemoryHandle();
850 } 834 }
851 #endif 835 #endif
852 836
853 // static 837 // static
854 void FieldTrialList::CopyFieldTrialStateToFlags( 838 void FieldTrialList::CopyFieldTrialStateToFlags(
855 const char* field_trial_handle_switch, 839 const char* field_trial_handle_switch,
856 const char* enable_features_switch, 840 const char* enable_features_switch,
857 const char* disable_features_switch, 841 const char* disable_features_switch,
858 CommandLine* cmd_line) { 842 CommandLine* cmd_line) {
859 // TODO(lawrencewu): Ideally, having the global would be guaranteed. However, 843 // TODO(lawrencewu): Ideally, having the global would be guaranteed. However,
860 // content browser tests currently don't create a FieldTrialList because they 844 // content browser tests currently don't create a FieldTrialList because they
861 // don't run ChromeBrowserMainParts code where it's done for Chrome. 845 // don't run ChromeBrowserMainParts code where it's done for Chrome.
862 // Some tests depend on the enable and disable features flag switch, though, 846 // Some tests depend on the enable and disable features flag switch, though,
863 // so we can still add those even though AllStatesToString() will be a no-op. 847 // so we can still add those even though AllStatesToString() will be a no-op.
864 if (!global_) { 848 if (!global_) {
865 AddFeatureAndFieldTrialFlags(enable_features_switch, 849 AddFeatureAndFieldTrialFlags(enable_features_switch,
866 disable_features_switch, cmd_line); 850 disable_features_switch, cmd_line);
867 return; 851 return;
868 } 852 }
869 853
870 // Use shared memory to pass the state if the feature is enabled, otherwise 854 // Use shared memory to pass the state if the feature is enabled, otherwise
871 // fallback to passing it via the command line as a string. 855 // fallback to passing it via the command line as a string.
872 if (kUseSharedMemoryForFieldTrials) { 856 if (kUseSharedMemoryForFieldTrials) {
873 InstantiateFieldTrialAllocatorIfNeeded(); 857 InstantiateFieldTrialAllocatorIfNeeded();
874 // If the readonly handle didn't get duplicated properly, then fallback to 858 // If the readonly handle didn't get duplicated properly, then fallback to
875 // original behavior. 859 // original behavior.
876 if (global_->readonly_allocator_handle_ == kInvalidPlatformFile) { 860 if (!global_->readonly_allocator_handle_.IsValid()) {
877 AddFeatureAndFieldTrialFlags(enable_features_switch, 861 AddFeatureAndFieldTrialFlags(enable_features_switch,
878 disable_features_switch, cmd_line); 862 disable_features_switch, cmd_line);
879 return; 863 return;
880 } 864 }
881 865
882 global_->field_trial_allocator_->UpdateTrackingHistograms(); 866 global_->field_trial_allocator_->UpdateTrackingHistograms();
883 867 std::string switch_value = SerializeSharedMemoryHandleMetadata(
884 #if defined(OS_WIN) 868 global_->readonly_allocator_handle_);
885 // We need to pass a named anonymous handle to shared memory over the 869 cmd_line->AppendSwitchASCII(field_trial_handle_switch, switch_value);
886 // command line on Windows, since the child doesn't know which of the
887 // handles it inherited it should open.
888 // PlatformFile is typedef'd to HANDLE which is typedef'd to void *. We
889 // basically cast the handle into an int (uintptr_t, to be exact), stringify
890 // the int, and pass it as a command-line flag. The child process will do
891 // the reverse conversions to retrieve the handle. See
892 // http://stackoverflow.com/a/153077
893 auto uintptr_handle =
894 reinterpret_cast<uintptr_t>(global_->readonly_allocator_handle_);
895 std::string field_trial_handle = std::to_string(uintptr_handle);
896 cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle);
897 #elif defined(OS_POSIX)
898 // On POSIX, we dup the fd into a fixed fd kFieldTrialDescriptor, so we
899 // don't have to pass over the handle (it's not even the right handle
900 // anyways). But some browser tests don't create the allocator, so we need
901 // to be able to distinguish valid and invalid handles. We do that by just
902 // checking that the flag is set with a dummy value.
903 cmd_line->AppendSwitchASCII(field_trial_handle_switch, "1");
904 #else
905 #error Unsupported OS
906 #endif
907 return; 870 return;
908 } 871 }
909 872
910 AddFeatureAndFieldTrialFlags(enable_features_switch, disable_features_switch, 873 AddFeatureAndFieldTrialFlags(enable_features_switch, disable_features_switch,
911 cmd_line); 874 cmd_line);
912 } 875 }
913 876
914 // static 877 // static
915 FieldTrial* FieldTrialList::CreateFieldTrial( 878 FieldTrial* FieldTrialList::CreateFieldTrial(
916 const std::string& name, 879 const std::string& name,
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
1125 std::vector<const FieldTrial::FieldTrialEntry*> entries; 1088 std::vector<const FieldTrial::FieldTrialEntry*> entries;
1126 FieldTrialAllocator::Iterator iter(&allocator); 1089 FieldTrialAllocator::Iterator iter(&allocator);
1127 const FieldTrial::FieldTrialEntry* entry; 1090 const FieldTrial::FieldTrialEntry* entry;
1128 while ((entry = iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) != 1091 while ((entry = iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) !=
1129 nullptr) { 1092 nullptr) {
1130 entries.push_back(entry); 1093 entries.push_back(entry);
1131 } 1094 }
1132 return entries; 1095 return entries;
1133 } 1096 }
1134 1097
1098 // static
1099 std::string FieldTrialList::SerializeSharedMemoryHandleMetadata(
1100 const SharedMemoryHandle& shm) {
1101 std::stringstream ss;
1102 #if defined(OS_WIN)
1103 // Tell the child process the name of the inherited HANDLE.
1104 uintptr_t uintptr_handle = reinterpret_cast<uintptr_t>(shm.GetHandle());
1105 ss << uintptr_handle << ",";
1106 #elif !defined(OS_POSIX)
1107 #error Unsupported OS
1108 #endif
1109
1110 base::UnguessableToken guid = shm.GetGUID();
1111 ss << guid.GetHighForSerialization() << "," << guid.GetLowForSerialization();
1112 return ss.str();
1113 }
1114
1135 #if defined(OS_WIN) 1115 #if defined(OS_WIN)
1136 // static 1116 // static
1137 bool FieldTrialList::CreateTrialsFromHandleSwitch( 1117 SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
1138 const std::string& handle_switch) { 1118 const std::string& switch_value) {
1139 int field_trial_handle = std::stoi(handle_switch); 1119 std::vector<std::string> tokens = base::SplitString(
Alexei Svitkine (slow) 2017/05/05 22:37:50 Nit: Use StringSplitPiece() version
erikchen 2017/05/08 23:37:49 Done.
1120 switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
1121
1122 if (tokens.size() != 3)
1123 return SharedMemoryHandle();
1124
1125 int field_trial_handle = 0;
1126 if (!base::StringToInt(tokens[0], &field_trial_handle))
1127 return SharedMemoryHandle();
1140 HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle); 1128 HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
1141 // TODO(erikchen): Plumb a GUID for this SharedMemoryHandle. 1129
1142 // https://crbug.com/713763. 1130 uint64_t high = 0;
1143 SharedMemoryHandle shm_handle(handle, base::UnguessableToken::Create()); 1131 if (!base::StringToUint64(tokens[1], &high))
1144 return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle); 1132 return SharedMemoryHandle();
1133 uint64_t low = 0;
1134 if (!base::StringToUint64(tokens[2], &low))
1135 return SharedMemoryHandle();
1136
1137 base::UnguessableToken guid = base::UnguessableToken::Deserialize(high, low);
1138 return SharedMemoryHandle(handle, guid);
1139 }
1140
1141 // static
1142 bool FieldTrialList::CreateTrialsFromSwitchValue(
1143 const std::string& switch_value) {
Alexei Svitkine (slow) 2017/05/05 22:37:50 Please match the same order in .cc as in .h file -
erikchen 2017/05/08 23:37:49 Done.
1144 SharedMemoryHandle shm = DeserializeSharedMemoryHandleMetadata(switch_value);
1145 if (!shm.IsValid())
1146 return false;
1147 return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
1145 } 1148 }
1146 #endif 1149 #endif
1147 1150
1148 #if defined(OS_POSIX) && !defined(OS_NACL) 1151 #if defined(OS_POSIX) && !defined(OS_NACL)
1149 // static 1152 // static
1150 bool FieldTrialList::CreateTrialsFromDescriptor(int fd_key) { 1153 SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
1154 int fd,
1155 const std::string& switch_value) {
1156 std::vector<std::string> tokens = base::SplitString(
1157 switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
1158
1159 if (tokens.size() != 2)
1160 return SharedMemoryHandle();
1161 uint64_t high = 0;
1162 if (!base::StringToUint64(tokens[0], &high))
1163 return SharedMemoryHandle();
1164 uint64_t low = 0;
1165 if (!base::StringToUint64(tokens[1], &low))
1166 return SharedMemoryHandle();
Alexei Svitkine (slow) 2017/05/05 22:37:50 Nit: I find the following a bit cleaner: uint64_t
erikchen 2017/05/08 23:37:49 Done.
1167
1168 base::UnguessableToken guid = base::UnguessableToken::Deserialize(high, low);
Alexei Svitkine (slow) 2017/05/05 22:37:51 Suggest making a helper anon function to share thi
erikchen 2017/05/08 23:37:49 Done.
1169 return SharedMemoryHandle(FileDescriptor(fd, true), guid);
1170 }
1171
1172 // static
1173 bool FieldTrialList::CreateTrialsFromDescriptor(
1174 int fd_key,
1175 const std::string& switch_value) {
1151 if (!kUseSharedMemoryForFieldTrials) 1176 if (!kUseSharedMemoryForFieldTrials)
1152 return false; 1177 return false;
1153 1178
1154 if (fd_key == -1) 1179 if (fd_key == -1)
1155 return false; 1180 return false;
1156 1181
1157 int fd = GlobalDescriptors::GetInstance()->MaybeGet(fd_key); 1182 int fd = GlobalDescriptors::GetInstance()->MaybeGet(fd_key);
1158 if (fd == -1) 1183 if (fd == -1)
1159 return false; 1184 return false;
1160 1185
1161 // TODO(erikchen): Plumb a GUID for this SharedMemoryHandle. 1186 SharedMemoryHandle shm =
1162 // https://crbug.com/713763. 1187 DeserializeSharedMemoryHandleMetadata(fd, switch_value);
1163 SharedMemoryHandle shm_handle(FileDescriptor(fd, true), 1188 if (!shm.IsValid())
1164 base::UnguessableToken::Create()); 1189 return false;
1165 1190
1166 bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle); 1191 bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
1167 DCHECK(result); 1192 DCHECK(result);
1168 return true; 1193 return true;
1169 } 1194 }
1170 #endif 1195 #endif
1171 1196
1172 // static 1197 // static
1173 bool FieldTrialList::CreateTrialsFromSharedMemoryHandle( 1198 bool FieldTrialList::CreateTrialsFromSharedMemoryHandle(
1174 SharedMemoryHandle shm_handle) { 1199 SharedMemoryHandle shm_handle) {
1175 // shm gets deleted when it gets out of scope, but that's OK because we need 1200 // shm gets deleted when it gets out of scope, but that's OK because we need
1176 // it only for the duration of this method. 1201 // it only for the duration of this method.
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
1247 } 1272 }
1248 1273
1249 // Add all existing features. 1274 // Add all existing features.
1250 FeatureList::GetInstance()->AddFeaturesToAllocator( 1275 FeatureList::GetInstance()->AddFeaturesToAllocator(
1251 global_->field_trial_allocator_.get()); 1276 global_->field_trial_allocator_.get());
1252 1277
1253 #if !defined(OS_NACL) 1278 #if !defined(OS_NACL)
1254 // Set |readonly_allocator_handle_| so we can pass it to be inherited and 1279 // Set |readonly_allocator_handle_| so we can pass it to be inherited and
1255 // via the command line. 1280 // via the command line.
1256 global_->readonly_allocator_handle_ = 1281 global_->readonly_allocator_handle_ =
1257 CreateReadOnlyHandle(global_->field_trial_allocator_.get()); 1282 global_->field_trial_allocator_->shared_memory()->GetReadOnlyHandle();
1258 #endif 1283 #endif
1259 } 1284 }
1260 1285
1261 // static 1286 // static
1262 void FieldTrialList::AddToAllocatorWhileLocked( 1287 void FieldTrialList::AddToAllocatorWhileLocked(
1263 PersistentMemoryAllocator* allocator, 1288 PersistentMemoryAllocator* allocator,
1264 FieldTrial* field_trial) { 1289 FieldTrial* field_trial) {
1265 // Don't do anything if the allocator hasn't been instantiated yet. 1290 // Don't do anything if the allocator hasn't been instantiated yet.
1266 if (allocator == nullptr) 1291 if (allocator == nullptr)
1267 return; 1292 return;
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
1359 return; 1384 return;
1360 } 1385 }
1361 AutoLock auto_lock(global_->lock_); 1386 AutoLock auto_lock(global_->lock_);
1362 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name(); 1387 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name();
1363 trial->AddRef(); 1388 trial->AddRef();
1364 trial->SetTrialRegistered(); 1389 trial->SetTrialRegistered();
1365 global_->registered_[trial->trial_name()] = trial; 1390 global_->registered_[trial->trial_name()] = trial;
1366 } 1391 }
1367 1392
1368 } // namespace base 1393 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698